home *** CD-ROM | disk | FTP | other *** search
/ IRIX Installation Tools & Overlays 2002 November / SGI IRIX Installation Tools & Overlays 2002 November - Disc 4.iso / dist / infosearch.idb / usr / lib / infosearch / bin / mknmz.z / mknmz
Text File  |  2002-10-15  |  69KB  |  2,473 lines

  1. #! /usr/bin/perl5 -w
  2. # -*- Perl -*-
  3. # mknmz - indexer of Namazu
  4. # $Id: mknmz,v 1.1 2002/08/14 15:51:10 agd Exp $
  5. #
  6. # Copyright (C) 1997-1999 Satoru Takabayashi All rights reserved.
  7. # Copyright (C) 2000,2001 Namazu Project All rights reserved.
  8. #     This is free software with ABSOLUTELY NO WARRANTY.
  9. #
  10. #  This program is free software; you can redistribute it and/or modify
  11. #  it under the terms of the GNU General Public License as published by
  12. #  the Free Software Foundation; either versions 2, or (at your option)
  13. #  any later version.
  14. #  This program is distributed in the hope that it will be useful
  15. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. #  GNU General Public License for more details.
  18. #
  19. #  You should have received a copy of the GNU General Public License
  20. #  along with this program; if not, write to the Free Software
  21. #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  22. #  02111-1307, USA
  23. #
  24. #  This file must be encoded in EUC-JP encoding
  25. #
  26.  
  27. package mknmz;
  28. require 5.004;
  29. use lib ".";
  30. use Cwd;
  31. use IO::File;
  32. use File::Find;
  33. require "/usr/lib/infosearch/l10n/namazu/pm/MMagic.pm";
  34. #use File::MMagic;
  35. use Time::Local;
  36. use strict;  # be strict since v1.2.0
  37. use Getopt::Long;
  38. use File::Copy;
  39. use DirHandle;
  40.  
  41. use vars qw($SYSTEM);
  42. $SYSTEM = $^O;
  43.  
  44. my $NAMAZU_INDEX_VERSION = "2.0";
  45.  
  46. my $CodingSystem = "euc";
  47. my $PKGDATADIR    = $ENV{'pkgdatadir'} || "/usr/lib/infosearch/l10n/namazu";
  48. my $CONFDIR       = "/usr/lib/infosearch/l10n/namazu/etc";     # directory where mknmzrc are in.
  49. my $LIBDIR        = $PKGDATADIR . "/pl";      # directory where library etc. are in.
  50. my $FILTERDIR     = $PKGDATADIR . "/filter";   # directory where filters are in.
  51. my $TEMPLATEDIR   = $PKGDATADIR . "/template"; # directory where templates are in.
  52.  
  53. my $DeletedFilesCount = 0;
  54. my $UpdatedFilesCount = 0;
  55. my $APPENDMODE = 0;
  56. my %PhraseHash = ();
  57. my %PhraseHashLast = ();
  58. my %KeyIndex = ();
  59. my %KeyIndexLast = ();
  60. my %CheckPoint = ("on" => undef, "continue" => undef);
  61. my $ConfigFile = undef;
  62. my $MediaType  = undef;
  63.  
  64. my $ReplaceCode  = undef;  # perl code for transforming URI
  65. my @Seed = ();
  66. my @LoadedRcfiles = ();
  67. my $Magic = new File::MMagic;
  68.  
  69. my $ReceiveTERM = 0;
  70.  
  71. STDOUT->autoflush(1);
  72. STDERR->autoflush(1);
  73. main();
  74. sub main {
  75.     my $start_time = time;
  76.  
  77.     init();
  78.  
  79.     # At first, loading pl/conf.pl to prevent overriding some variables.
  80.     preload_modules();
  81.  
  82.     # set LANG and bind textdomain
  83.     util::set_lang();
  84.     textdomain('namazu', $util::LANG_MSG);
  85.  
  86.     load_rcfiles();
  87.     load_modules();
  88.     my ($output_dir, @targets) = parse_options();
  89.     my ($docid_base, $total_files_num) = prep($output_dir, @targets);
  90.  
  91.     my $swap = 1;
  92.     my $docid_count = 0;
  93.     my $file_count = 0;
  94.     my $total_files_size = 0;
  95.     my $key_count = 0;
  96.     my $checkpoint = 0;
  97.     my $flist_ptr = 0;
  98.     my $processed_files_size = 0;
  99.  
  100.     if ($CheckPoint{'continue'}) {
  101.     # Restore variables
  102.     eval util::readfile($var::NMZ{'_checkpoint'}) ;
  103.     } else {
  104.     print $total_files_num . _(" files are found to be indexed.\n");
  105.     }
  106.  
  107.     {
  108.     my $fh_errorsfile = util::efopen(">>$var::NMZ{'err'}");
  109.     my $fh_flist = util::efopen($var::NMZ{'_flist'});
  110.     my %field_indices = ();
  111.     get_field_index_base(\%field_indices);
  112.  
  113.     if ($CheckPoint{'continue'}) {
  114.         seek($fh_flist, $flist_ptr, 0);
  115.     }
  116.  
  117.     # Process target files one by one
  118.     while (defined(my $line = <$fh_flist>)) {
  119.         $flist_ptr += length($line);
  120.         my $cfile = $line;
  121.         chomp $cfile;
  122.         util::dprint(_("target file: ")."$cfile\n");
  123.  
  124.         my ($cfile_size, $num) = 
  125.         process_file($cfile, $docid_count, $docid_base, 
  126.                  $file_count, \%field_indices,
  127.                  $fh_errorsfile, $total_files_num);
  128.         if ($num == 0) {
  129.         $total_files_num--;
  130.         next;
  131.         } else {
  132.         $docid_count += $num;
  133.         $file_count++;
  134.         }
  135.  
  136.         $total_files_size     += $cfile_size;
  137.         $processed_files_size += $cfile_size;
  138.         last if $ReceiveTERM;
  139.         if ($processed_files_size > $conf::ON_MEMORY_MAX) {
  140.         if (%KeyIndex) {
  141.             $key_count = write_index();
  142.             print _("Writing index files..."); 
  143.             write_phrase_hash();
  144.             print "\n";
  145.         }
  146.         $processed_files_size = 0;
  147.         $checkpoint = 1, last if $CheckPoint{'on'} && defined(<$fh_flist>);
  148.         }
  149.     }
  150.     }
  151.     # This should be out of above blocks because of file handler closing.
  152.     re_exec($flist_ptr, $docid_count, $docid_base, $start_time, 
  153.         $total_files_size, $total_files_num,
  154.         $file_count, $key_count) if $checkpoint;
  155.  
  156.     if (%KeyIndex) {
  157.     $key_count = write_index();
  158.     print _("Writing index files...");
  159.     write_phrase_hash();
  160.     print "\n";
  161.     }
  162.  
  163.     $key_count = get_total_keys() unless $key_count;
  164.     do_remain_job($total_files_size, $docid_count, $key_count, 
  165.            $start_time);
  166.     exit 0;
  167. }
  168.  
  169. #
  170. # FIXME: Very complicated.
  171. #
  172. sub process_file ($$$$$$) {
  173.     my ($cfile, $docid_count, $docid_base, $file_count, 
  174.     $field_indices, $fh_errorsfile, $total_files_num) = @_;
  175.  
  176.     my $processed_num = 0;
  177.     my $file_size = util::filesize($cfile);
  178.  
  179.     if ($var::Opt{'htmlsplit'} && $cfile =~ $conf::HTML_SUFFIX) {
  180.     my @parts = htmlsplit::split($cfile, "NMZ.partial");
  181.     if (@parts > 1) {
  182.         my $id = 0;
  183.         for my $part (@parts) {
  184.         my $fname = util::tmpnam("NMZ.partial.$id");
  185.         my $fragment  = defined $part ? $part : undef;
  186.         my $uri   = generate_uri($cfile, $fragment);
  187.         my $result = namazu_core($fname, 
  188.                      $docid_count + $processed_num, 
  189.                      $docid_base, $file_count, 
  190.                      $field_indices, $fh_errorsfile, 
  191.                      $total_files_num, 
  192.                      $uri, $id, $#parts);
  193.         if ($result > 0) {
  194.             $processed_num++;
  195.             my $rname = defined $part ? "$cfile\t$part" : "$cfile";
  196.             put_registry($rname);
  197.         }
  198.             unlink $fname;
  199.         $id++;
  200.         }
  201.         return ($file_size, $processed_num);
  202.     }
  203.     }
  204.     my $result = namazu_core($cfile, $docid_count, $docid_base, 
  205.                  $file_count, $field_indices, 
  206.                  $fh_errorsfile, $total_files_num,
  207.                  undef, undef, undef);
  208.     if ($result > 0) {
  209.     $processed_num++;
  210.     put_registry($cfile);
  211.     }
  212.  
  213.     return ($file_size, $processed_num);
  214. }
  215.  
  216. # Load mknmzrcs:
  217. #  1. MKNMZRC environment
  218. #  2. $(sysconfdir)/$(PACKAGE)/mknmzrc
  219. #  3. ~/.mknmzrc
  220. #  4. user-specified mknmzrc set by mknmz --config=file option.
  221. # If multiple files exists, read all of them.
  222. sub load_rcfiles () {
  223.     my (@cand) = ();
  224.  
  225.     # To support Windows. Since they have nasty drive letter convention,
  226.     # it is necessary to change mknmzrc dynamically with env. variable.
  227.     push @cand, $ENV{'MKNMZRC'} if defined $ENV{'MKNMZRC'};
  228.     push @cand, "$CONFDIR/mknmzrc";
  229.     push @cand, "$ENV{'HOME'}/.mknmzrc";
  230.  
  231.     util::vprint(_("Reading rcfile: "));
  232.     for my $rcfile (@cand) {
  233.     if (-f $rcfile) {
  234.         load_rcfile ($rcfile);
  235.         util::vprint(" $rcfile");
  236.     }
  237.     }
  238.     util::vprint("\n");
  239. }
  240.  
  241. sub load_rcfile ($) {
  242.     my ($rcfile) = @_;
  243.     if ($SYSTEM eq "MSWin32" || $SYSTEM eq "os2") {
  244.     # convert \ to / with consideration for Shift_JIS Kanji code
  245.     $rcfile =~ 
  246.         s!([\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]|[\x01-\x7f])!
  247.         $1 eq "\\" ? "/" : $1!gex;
  248.     }
  249.     return if (grep {m/^$rcfile$/} @LoadedRcfiles);
  250.     do $rcfile;
  251.     push @LoadedRcfiles, $rcfile;
  252.  
  253.     # Dirty workaround.
  254.     $LIBDIR = $conf::LIBDIR
  255.     if (defined $conf::LIBDIR && -d $conf::LIBDIR);
  256.     $FILTERDIR = $conf::FILTERDIR
  257.     if (defined $conf::FILTERDIR && -d $conf::FILTERDIR);
  258.     $TEMPLATEDIR = $conf::TEMPLATEDIR
  259.     if (defined $conf::TEMPLATEDIR && -d $conf::TEMPLATEDIR);
  260. }
  261.  
  262. sub re_exec($$$$$$$$) {
  263.     my ($flist_ptr, $docid_count, $docid_base, $start_time, 
  264.     $total_files_size, $total_files_num, $file_count, $key_count) = @_;
  265.  
  266.     # store variables
  267.     {
  268.     my $fh_checkpoint = util::efopen(">$var::NMZ{'_checkpoint'}");
  269.  
  270.     print $fh_checkpoint <<EOM;
  271.     \$DeletedFilesCount = $DeletedFilesCount;
  272.     \$UpdatedFilesCount = $UpdatedFilesCount;
  273.     \$APPENDMODE = $APPENDMODE;
  274.     \$flist_ptr = $flist_ptr;
  275.     \$docid_count = $docid_count;
  276.     \$docid_base = $docid_base;
  277.     \$start_time = $start_time;
  278.     \$total_files_size = $total_files_size;
  279.     \$total_files_num = $total_files_num;
  280.     \$key_count = $key_count;
  281.     \$file_count = $file_count;
  282.     \$\$ = $$;
  283. EOM
  284.     }
  285.  
  286.     @ARGV = ("-S", @ARGV) ;
  287.     print _("Checkpoint reached: re-exec mknmz...\n");
  288.     util::dprint(join ' ', ("::::", @ARGV, "\n"));
  289.     exec ($0, @ARGV) ;
  290. }
  291.  
  292. sub put_registry ($) {
  293.     my ($filename) = @_;
  294.     my $fh_registry = util::efopen(">>$var::NMZ{'_r'}");
  295.     print $fh_registry $filename, "\n";
  296. }
  297.  
  298.  
  299. # Initialization
  300. #   $CodingSystem: Character Coding System 'euc' or 'sjis'
  301. sub init () {
  302.     $SYSTEM = $^O;
  303.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  304.     $CodingSystem = "sjis";
  305.     if ($CONFDIR !~ /^[A-Z]:|^\\\\/i && $0 =~ m#^([A-Z]:)(/|\\)#i) {
  306.         $CONFDIR = $1 . $CONFDIR ;
  307.     }
  308.     if ($LIBDIR !~ /^[A-Z]:|^\\\\/i && $0 =~ m#^([A-Z]:)(/|\\)#i) {
  309.         $LIBDIR = $1 . $LIBDIR ;
  310.     }
  311.     if ($FILTERDIR !~ /^[A-Z]:|^\\\\/i && $0 =~ m#^([A-Z]:)(/|\\)#i) {
  312.         $FILTERDIR = $1 . $FILTERDIR ;
  313.     }
  314.     if ($TEMPLATEDIR !~ /^[A-Z]:|^\\\\/i && $0 =~ m#^([A-Z]:)(/|\\)#i) {
  315.         $TEMPLATEDIR = $1 . $TEMPLATEDIR ;
  316.     }
  317.     } else {
  318.     $CodingSystem = "euc";
  319.     }
  320.  
  321.     $SIG{'INT'}  = sub {
  322.     util::cdie("SIGINT caught! Aborted.\n");
  323.     };
  324.  
  325.     $SIG{'TERM'}  = sub {
  326.     print STDERR "SIGTERM caught!\n";
  327.     $ReceiveTERM = 1;
  328.     };
  329. }
  330.  
  331. sub preload_modules () { 
  332.     unshift @INC, $LIBDIR;
  333.     # workaround for test suites.
  334.     unshift @INC, $ENV{'top_builddir'} . "/pl" if defined $ENV{'top_builddir'};
  335.  
  336.     require "conf.pl" || die "unable to require \"conf.pl\"\n";
  337.     require "util.pl" || die "unable to require \"util.pl\"\n";
  338.     require "gettext.pl" || die "unable to require \"gettext.pl\"\n";
  339. }
  340.  
  341. sub postload_modules () {
  342.     require "htmlsplit.pl" || die "unable to require \"htmlsplit.pl\"\n";
  343. }
  344.  
  345. sub load_modules () {
  346.     require "var.pl" || die "unable to require \"var.pl\"\n";
  347.     require "usage.pl" || die "unable to require \"usage.pl\"\n";
  348.     require "codeconv.pl" || die "unable to require \"codeconv.pl\"\n";
  349.     require "wakati.pl" || die "unable to require \"wakati.pl\"\n";
  350.     require "seed.pl" || die "unable to require \"seed.pl\"\n";
  351.     require "gfilter.pl" || die "unable to require \"gfilter.pl\"\n";
  352.  
  353.     @Seed = seed::init();
  354. }
  355.  
  356. sub load_filtermodules () {
  357.     unshift @INC, $FILTERDIR;
  358.  
  359.     #
  360.     # Windows modules must be loaded first.
  361.     # Because OLE filters have low precedence over normal ones.
  362.     #
  363.     load_win32modules() if $SYSTEM eq "MSWin32";
  364.  
  365.     # Check filter modules
  366.     my @filters = ();
  367.     @filters = glob "$FILTERDIR/*.pl";
  368.  
  369.     load_filters(@filters);
  370. }
  371.  
  372. sub load_win32modules () {
  373.     # Check filter modules
  374.     my @filters = ();
  375.     if (-f "../filter/win32/olemsword.pl") { # to ease developing
  376.     @filters = glob "../filter/win32/*.pl";
  377.     unshift @INC, "../filter/win32";
  378.     } else {
  379.     @filters = glob "$FILTERDIR/win32/*.pl";
  380.     unshift @INC, "$FILTERDIR/win32";
  381.     }
  382.  
  383.     load_filters(@filters);
  384. }
  385.  
  386. sub load_filters (@) {
  387.     my @filters = @_;
  388.    
  389.     for my $filter (@filters) {
  390.     $filter =~ m!([-\w]+)\.pl$!;
  391.     my $module = $1;
  392.     require "$module.pl" || die "unable to require \"$module.pl\"\n";;
  393.     my (@mtypes, $status, $recursive, $pre_codeconv, $post_codeconv);
  394.  
  395.     eval "\@mtypes =    ${module}::mediatype();";
  396.     die $@ if $@;  # eval error
  397.     eval "\$status =    ${module}::status();";
  398.     die $@ if $@;
  399.     eval "\$recursive = ${module}::recursive();";
  400.     die $@ if $@;
  401.     eval "\$pre_codeconv  = ${module}::pre_codeconv();";
  402.     die $@ if $@;
  403.     eval "\$post_codeconv  = ${module}::post_codeconv();";
  404.     die $@ if $@;
  405.     eval "${module}::add_magic(\$Magic);";
  406.     die $@ if $@;
  407.  
  408.     for my $mt (@mtypes) {
  409.         next if (defined $var::Supported{$mt} && 
  410.                  $var::Supported{$mt} eq 'yes' && $status eq 'no');
  411.         $var::Supported{$mt} = $status;
  412.         $var::REQUIRE_ACTIONS{$mt} = $module;
  413.         $var::RECURSIVE_ACTIONS{$mt} = $recursive;
  414.         $var::REQUIRE_PRE_CODECONV{$mt} = $pre_codeconv;
  415.         $var::REQUIRE_POST_CODECONV{$mt} = $post_codeconv;
  416.     }
  417.     }
  418. }
  419.  
  420. # Core routine.
  421. #
  422. # FIXME: Too many parameters. They must be cleared.
  423. #
  424. sub namazu_core ($$$$$$$$$$) {
  425.     my ($cfile, $docid_count, $docid_base, 
  426.     $file_count, $field_indices, $fh_errorsfile, $total_files_num,
  427.     $uri, $part_id, $part_num) = @_;
  428.  
  429.     my $headings = "";
  430.     my $content = "";
  431.     my $weighted_str = "";
  432.     my %fields;
  433.     my $msg_prefix;
  434.  
  435.     if ($part_id) {
  436.     $msg_prefix = "    $part_id/$part_num - ";
  437.     } else {
  438.     $msg_prefix = $file_count + 1 . "/$total_files_num - ";
  439.     }
  440.  
  441.     unless ($uri) {
  442.     $uri = generate_uri($cfile);  # Make a URI from a file name.
  443.     }
  444.     my ($cfile_size, $text_size, $kanji, $mtype) = 
  445.     load_document(\$cfile, \$content, \$weighted_str,
  446.               \$headings, \%fields);
  447.  
  448.     util::dprint(_("after load_document: ")."$uri: $cfile_size, $text_size, $kanji, $mtype\n");
  449.  
  450.     # Check if the file is acceptable.
  451.     my $err = check_file($cfile, $cfile_size, $text_size, $mtype, $uri);
  452.     if (defined $err) {
  453.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  454.         my $uri2 = codeconv::eucjp_to_shiftjis($uri);
  455.         print $msg_prefix . "$uri2 $err\n";
  456.     } else {
  457.         print $msg_prefix . "$uri $err\n";
  458.     }
  459.     print $fh_errorsfile "$cfile $err\n"; 
  460.     return 0;  # return 0 if error
  461.     }
  462.  
  463.     # Print processing file name as URI.
  464.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  465.     my $uri2 = codeconv::eucjp_to_shiftjis($uri);
  466.     print $msg_prefix . "$uri2 [$mtype]\n";
  467.     } else {
  468.     print $msg_prefix . "$uri [$mtype]\n";
  469.     }
  470.  
  471.     complete_field_info(\%fields, $cfile, $uri, 
  472.             \$headings, \$content, \$weighted_str);
  473.     put_field_index(\%fields, $field_indices);
  474.  
  475.     put_dateindex($cfile);
  476.     $content .= $weighted_str;   # add weights
  477.     count_words($docid_count, $docid_base, \$content, $kanji);
  478.     make_phrase_hash($docid_count, $docid_base, \$content);
  479.  
  480.     # assertion
  481.     util::assert($cfile_size != 0, 
  482.          "cfile_size == 0 at the end of namazu_core.");
  483.  
  484.     return $cfile_size;
  485. }
  486.  
  487. #
  488. # Make the URI from the given file name.
  489. #
  490. sub generate_uri (@) {
  491.     my ($file, $fragment) = @_;
  492.     return "" unless defined $file;
  493.  
  494.     # omit a file name if omittable
  495.     $file =~ s!^(.*)/($conf::DIRECTORY_INDEX)$!$1/!o; 
  496.  
  497.     if (defined $ReplaceCode) {
  498.     # transforming URI by evaling
  499.     $_ = $file;
  500.     eval $ReplaceCode;
  501.     $file = $_;
  502.     }
  503.  
  504.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  505.     $file =~ s#^([A-Z]):#/$1|#i; # converting a drive part like: /C|
  506.     }
  507.  
  508.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  509.     $file = codeconv::shiftjis_to_eucjp($file);
  510.     }
  511.     if (defined $fragment) {
  512.         codeconv::toeuc(\$fragment);
  513.     }
  514.  
  515.     unless ($var::Opt{'noencodeuri'}) {
  516.     for my $tmp ($file, $fragment) {
  517.         next unless defined $tmp;
  518.  
  519.         # Escape unsafe characters (not strict)
  520.         $tmp =~ s/\%/%25/g;  # Convert original '%' into '%25' v1.1.1.2
  521.         $tmp =~ s/([^a-zA-Z0-9~\-\_\.\/\:\%])/
  522.         sprintf("%%%02X",ord($1))/ge;
  523.     }
  524.     }
  525.  
  526.  
  527.     my $uri = $file;
  528.     $uri .= "#" . $fragment if defined $fragment;
  529.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  530.         # restore '|' for drive letter rule of Win32, OS/2
  531.         $uri =~ s!^/([A-Z])%7C!/$1|!i;
  532.     }
  533.     return $uri;
  534. }
  535.  
  536.  
  537. sub get_field_index_base (\%) {
  538.     my ($field_indices) = @_;
  539.  
  540.     my @keys = split('\|', $conf::SEARCH_FIELD);
  541.     if ($var::Opt{'meta'}) {
  542.     push @keys, (split '\|', $conf::META_TAGS);
  543.     }
  544.     for my $key (@keys) {
  545.     $key = lc($key);
  546.     my $fname    = "$var::NMZ{'field'}.$key";
  547.     my $tmp_fname = util::tmpnam("NMZ.field.$key");
  548.     my $size = 0;
  549.     $size = -s $fname if -f $fname;
  550.     $size += -s $tmp_fname if -f $tmp_fname;
  551.     $field_indices->{$key} = $size;
  552.     }
  553. }
  554.  
  555. sub complete_field_info (\%$$\$\$\$) {
  556.     my ($fields, $cfile, $uri, $headings, $contref, $wsref) = @_;
  557.  
  558.     unless (defined($fields->{'title'})) {
  559.     $fields->{'title'} = gfilter::filename_to_title($cfile, $wsref);
  560.     }
  561.     unless (defined($fields->{'date'})) {
  562.     my $mtime = (stat($cfile))[9];
  563.     my $date = util::rfc822time($mtime);
  564.     $fields->{'date'} = $date;
  565.     }
  566.     unless (defined($fields->{'uri'})) {
  567.     $fields->{'uri'} = $uri;
  568.     }
  569.     unless (defined($fields->{'size'})) {
  570.     $fields->{'size'} = -s $cfile;
  571.     }
  572.     unless (defined($fields->{'summary'})) {
  573.     $fields->{'summary'} = make_summary($contref, $headings, $cfile);
  574.     }
  575.     unless (defined($fields->{'from'}) || defined($fields->{'author'})) {
  576.     $fields->{'from'} = getmsg("unknown");
  577.     }
  578. }
  579.  
  580. #
  581. # Currently, messages for NMZ.* files should be encoded in
  582. # EUC-JP currently. We cannot use gettext.pl for the messsage
  583. # because gettext.pl may use Shift_JIS encoded messages.
  584. # So, we should use the function instead of gettext().
  585. #
  586. # FIXME: Ad hoc impl.  getmsg() is effective only for "unknown".
  587. #
  588. sub getmsg($) {
  589.     my ($msg) = @_;
  590.  
  591.     if (util::islang_msg("ja")) {
  592.     if ($msg eq "unknown") {
  593.         return "╔╘╠└";
  594.     }
  595.     }
  596.     return $msg;
  597. }
  598.  
  599. sub make_summary ($$$) {
  600.     my ($contref, $headings, $cfile) = @_;
  601.  
  602.     # pick up $conf::MAX_FIELD_LENGTH bytes string
  603.     my $tmp = "";
  604.     if ($$headings ne "") {
  605.     $$headings =~ s/^\s+//;
  606.     $$headings =~ s/\s+/ /g;
  607.     $tmp = $$headings;
  608.     } else {
  609.     $tmp = "";
  610.     }
  611.  
  612.     my $offset = 0;
  613.     my $tmplen = 0;
  614.     while (($tmplen = $conf::MAX_FIELD_LENGTH + 1 - length($tmp)) > 0
  615.            && $offset < length($$contref))
  616.     {
  617.         $tmp .= substr $$contref, $offset, $tmplen;
  618.         $offset += $tmplen;
  619.         $tmp =~ s/(([\xa1-\xfe]).)/$2 eq "\xa8" ? '': $1/ge;
  620.         $tmp =~ s/([-=*\#])\1{2,}/$1$1/g;
  621.     }
  622.  
  623.     # -1 means "LF"
  624.     my $summary = substr $tmp, 0, $conf::MAX_FIELD_LENGTH - 1;
  625.     # Remove a garbage Kanji 1st char at the end.
  626.     $summary = codeconv::chomp_eucjp($summary);
  627.  
  628.     $summary =~ s/^\s+//;
  629.     $summary =~ s/\s+/ /g;   # normalize white spaces
  630.  
  631.     return $summary;
  632. }
  633.  
  634.  
  635. # output the field infomation into NMZ.fields.* files
  636. sub put_field_index (\%$) {
  637.     my ($fields, $field_indices) = @_;
  638.  
  639.     my $aliases_regex = 
  640.     join('|', sort {length($b) <=> length($a)} keys %conf::FIELD_ALIASES);
  641.  
  642.     for my $field (keys %{$fields}) {
  643.         util::dprint("Field: $field: $fields->{$field}\n");
  644.     if ($field =~ /^($aliases_regex)$/o) {
  645.         unless (defined($fields->{$conf::FIELD_ALIASES{$field}})) {
  646.         $fields->{$conf::FIELD_ALIASES{$field}} = $fields->{$field};
  647.         }
  648.         undef $fields->{$field};
  649.     }
  650.     }
  651.  
  652.     my @keys = split '\|', $conf::SEARCH_FIELD;
  653.     if ($var::Opt{'meta'}) {
  654.     push @keys, (split '\|', $conf::META_TAGS);
  655.  
  656.         # uniq @keys
  657.         my %mark = ();
  658.         @keys = grep {$mark{$_}++; $mark{$_} == 1} @keys;
  659.     }
  660.     for my $key (@keys) {
  661.     my $lkey = lc($key);
  662.     my $fname    = util::tmpnam("NMZ.field.$lkey");
  663.     my $fh_field = util::efopen(">>$fname");
  664.     my $output = "";
  665.     if (defined($fields->{$key})) {
  666.         if ($key ne 'uri') { # workaround for namazu-bugs-ja#30
  667.         $fields->{$key} =~ s/\s+/ /g;
  668.         $fields->{$key} =~ s/\s+$//;
  669.         $fields->{$key} =~ s/^\s+//;
  670.         }
  671.         $output = $fields->{$key};
  672.  
  673.         # -1 means "LF"
  674.         $output = substr $output, 0, $conf::MAX_FIELD_LENGTH - 1;
  675.         # Remove a garbage Kanji 1st char at the end.
  676.         $output = codeconv::chomp_eucjp($output);
  677.  
  678.         $output .= "\n";
  679.     } else {
  680.         $output = "\n";
  681.     }
  682.     print $fh_field $output;
  683.  
  684.     # put index of field index
  685.     {
  686.         my $fname        = util::tmpnam("NMZ.field.$lkey.i");
  687.         my $fh_field_idx = util::efopen(">>$fname");
  688.         print $fh_field_idx pack("N", $field_indices->{$lkey});
  689.         $field_indices->{$lkey} += length $output;
  690.     }
  691.     }
  692.  
  693. }
  694.  
  695. # put the date infomation into NMZ.t file
  696. sub put_dateindex ($) {
  697.     my ($cfile) = @_;
  698.     my $mtime = (stat($cfile))[9];
  699.  
  700.     my $fh_dataindex = util::efopen(">>$var::NMZ{'_t'}");
  701.     print $fh_dataindex pack("N", $mtime);
  702. }
  703.  
  704.  
  705. # load a document file
  706. sub load_document ($$$$\%) {
  707.     my ($orig_cfile, $contref, $weighted_str, $headings, $fields)
  708.       = @_;
  709.     my $cfile = $$orig_cfile;
  710.  
  711.     return (0, 0, 0, 0) unless (-f $cfile && -r $cfile);
  712.  
  713.     # for handling a filename which contains Shift_JIS code
  714.     my $shelter_cfile = "";
  715.     if ($SYSTEM eq "MSWin32" 
  716.     && $cfile =~ /[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]|[\x20\xa1-\xdf]/) 
  717.     {
  718.     $shelter_cfile = $cfile;
  719.     $cfile = util::tmpnam("NMZ.win32");
  720.         copy($shelter_cfile, $cfile);
  721.     }
  722.  
  723.     my $file_size;
  724.     $file_size = util::filesize($cfile); # not only file in feature.
  725.     if ($file_size > $conf::FILE_SIZE_MAX) {
  726.     return ($file_size, $file_size, 0, 'x-system/x-error');
  727.     }
  728.  
  729.     $$contref = util::readfile($cfile);
  730. #    $file_size = length($$contref);
  731.  
  732.     # Filtering process.
  733.     my $mtype;
  734.     my $called_dt = 0;
  735.     while (1) {
  736.     if (defined $MediaType) {
  737.         $mtype = $MediaType;
  738.     } else {
  739.         my $mtype_n;
  740.         if ($shelter_cfile ne "") {
  741.         $mtype_n = $Magic->checktype_byfilename($shelter_cfile);
  742.         } else {
  743.         $mtype_n = $Magic->checktype_byfilename($cfile);
  744.         }
  745.         my $mtype_c = $Magic->checktype_data($$contref);
  746.         my $mtype_m;
  747.         $mtype_m = $Magic->checktype_magic($$contref) 
  748.           if ((! defined $mtype_c) ||
  749.           $mtype_c =~ 
  750.           /^(text\/html|text\/plain|application\/octet-stream)$/);
  751.         $mtype_c = $mtype_m 
  752.         if (defined $mtype_m && 
  753.             $mtype_m !~ 
  754.             /^(text\/html|text\/plain|application\/octet-stream)$/);
  755.         $mtype_c = 'text/plain' unless defined $mtype_c;
  756.         if ($called_dt) {
  757.         $mtype = $mtype_c;
  758.         } else {
  759.         $mtype = decide_type($mtype_n, $mtype_c);
  760.         $called_dt = 1;
  761.         }
  762.     }
  763.     util::dprint(_("Detected type: ")."$mtype\n");
  764.  
  765.     # Pre code conversion.
  766.     if ($var::REQUIRE_PRE_CODECONV{$mtype}) {
  767.         util::dprint("pre_codeconv\n");
  768.         codeconv_document($contref);
  769.     }
  770.  
  771.     if (! $var::Supported{$mtype} || 
  772.         $var::Supported{$mtype} ne 'yes') 
  773.     {
  774.         util::vprint(_("Unsupported media type ")."$mtype\n");
  775.         return ($file_size, $file_size, 0, "$mtype; x-system=unsupported");
  776.     }
  777.  
  778.     if ($var::REQUIRE_ACTIONS{$mtype}) {
  779.         util::vprint(_("Using ")."$var::REQUIRE_ACTIONS{$mtype}.pl\n");
  780.         require $var::REQUIRE_ACTIONS{$mtype}.'.pl'
  781.             || die _("unable to require ") . 
  782.             "\"$var::REQUIRE_ACTIONS{$mtype}.pl\"\n";
  783.         my $err = undef;
  784.         eval '$err = ' . $var::REQUIRE_ACTIONS{$mtype} .
  785.           '::filter($orig_cfile, $contref, $weighted_str, $headings, $fields);';
  786.         if ($err) {
  787.         return ($file_size, $file_size, 0, "$mtype; x-error=$err");
  788.         }
  789.  
  790.         if ($@) {
  791.         util::vprint(_("Failed to call ")."$var::REQUIRE_ACTIONS{$mtype}\n$@\n");
  792.         return ($file_size, $file_size, 0, "$mtype; x-error=$@");
  793.         }
  794.  
  795.         # Post code conversion.
  796.         if ($var::REQUIRE_POST_CODECONV{$mtype}) {
  797.             util::dprint("post_codeconv\n");
  798.         codeconv_document($contref);
  799.         }
  800.  
  801.         next if ($var::RECURSIVE_ACTIONS{$mtype});
  802.     }
  803.     last;
  804.     }
  805.  
  806.     # Measure the text size at this time.
  807.     my $text_size = length($$contref) + length($$weighted_str); 
  808.  
  809.     if ($SYSTEM eq "MSWin32" && $shelter_cfile ne "") {
  810.     unlink $cfile;
  811.     $cfile = $shelter_cfile;
  812.     }
  813.  
  814.     my $kanji = $$contref =~ tr/\xa1-\xfe/\xa1-\xfe/;  # Kanji contained?
  815.     $kanji += $$weighted_str =~ tr/\xa1-\xfe/\xa1-\xfe/;
  816.  
  817.     return ($file_size, $text_size, $kanji, $mtype);
  818. }
  819.  
  820. sub codeconv_document ($) {
  821.     my ($textref) = @_;
  822.     codeconv::toeuc($textref);
  823.     $$textref =~ s/\r\n/\n/g;
  824.     $$textref =~ s/\r/\n/g;
  825. }
  826.  
  827. sub prep () {
  828.     my $docid_base = 0;
  829.     my $output_dir = shift @_ ;
  830.     my @targets = @_ ;
  831.     my @flist = ();
  832.  
  833.     $var::OUTPUT_DIR = $output_dir;
  834.  
  835.     require_modules();
  836.     change_filenames();
  837.     check_present_index();
  838.  
  839.     # if Checkpoint mode, return
  840.     return (0, 0) if $CheckPoint{'continue'};
  841.  
  842.     check_lockfile($var::NMZ{'lock2'});
  843.     print _("Looking for indexing files...\n");
  844.     @flist = find_target(@targets);
  845.     ($docid_base, @flist) = append_index(@flist) 
  846.     if -f $var::NMZ{'r'};
  847.     unless (@flist) { # if @flist is empty
  848.     print _("No files to index.\n");
  849.     exit 0;
  850.     }
  851.     set_lockfile($var::NMZ{'lock2'});
  852.     save_flist(@flist);
  853.     my $total_files_num = @flist;
  854.  
  855.     return ($docid_base, $total_files_num);
  856. }
  857.  
  858. sub save_flist(@) {
  859.     my @flist = @_;
  860.     return if (@flist == 0);
  861.  
  862.     my $fh_flist = util::efopen(">$var::NMZ{'_flist'}");
  863.     print $fh_flist join("\n", @flist), "\n";
  864. }
  865.  
  866. sub require_modules() {
  867.     if (util::islang("ja") && $conf::NKF =~ /^module_nkf/) {
  868.     require NKF || die "unable to require \"NKF\"\n";
  869.         util::dprint(_("code conversion: using NKF module\n"));
  870.     $var::USE_NKF_MODULE = 1;
  871.     }
  872.     if (util::islang("ja") && $conf::WAKATI =~ /^module_kakasi/) {
  873.     require Text::Kakasi || die "unable to require \"Text::Kakasi\"\n";
  874.         util::dprint(_("wakati: using Text::Kakasi module\n"));
  875.     my $res = Text::Kakasi::getopt_argv('kakasi', '-ieuc', '-oeuc', '-w');
  876.     }
  877.     if (util::islang("ja") && $conf::WAKATI =~ /^module_chasen/) {
  878.     require Text::ChaSen || die "unable to require \"Text::ChaSen\"\n";
  879.         util::dprint(_("wakati: using Text::ChaSen module\n"));
  880.     my @arg = ('-j', '-F', '%m ');
  881.     @arg    = ('-j', '-F', '%m %H\\n') if $var::Opt{'noun'};
  882.     my $res = Text::ChaSen::getopt_argv('chasen-perl', @arg);
  883.     }
  884. }
  885.  
  886. sub check_lockfile ($) {
  887.     # warn if check file exists in case other process is running or abnormal
  888.     # stop execution (later is not the major purpose, though).
  889.     # This is mainly for early detection before longish find_target.
  890.     my ($file) = @_;
  891.  
  892.     if (-f $file) {
  893.     print "$file "._("found. Maybe this index is being updated by another process now.\nIf not, you can remove this file.\n");
  894.     exit 1;
  895.     }
  896. }
  897.  
  898. sub set_lockfile ($) {
  899.     my ($file) = @_;
  900.  
  901.     # make a lock file
  902.     if (-f $file) {
  903.     print "$file found. Maybe this index is being updated by another process now.\nIf not, you can remove this file.\n";
  904.     exit 1;
  905.     } else {
  906.     my $fh_lockfile = util::efopen(">$file");
  907.     print $fh_lockfile "$$"; # save pid
  908.     }
  909. }
  910.  
  911. sub remove_lockfile ($) {
  912.     my ($file) = @_;
  913.  
  914.     # remove lock file
  915.     unlink $file if -f $file;
  916. }
  917.  
  918. # check present index whether it is old type of not
  919. sub check_present_index () {
  920.     if (-f $var::NMZ{'i'} && ! -f "$var::NMZ{'result'}.normal") 
  921.     {
  922.     util::cdie(_("Present index is old type. it's unsupported.\n"));
  923.     }
  924. }
  925.  
  926. # remain
  927. sub do_remain_job ($$$$) {
  928.     my ($total_files_size, $docid_count, $key_count, $start_time) = @_;
  929.  
  930.     if ($docid_count == 0) {
  931.     # No files are indexed
  932.     if ($DeletedFilesCount > 0) {
  933.         update_dateindex();
  934.         update_registry($docid_count);
  935.     }
  936.     } else {
  937.     set_lockfile($var::NMZ{'lock'});
  938.     write_version();
  939.     write_body_msg();
  940.     write_tips_msg();
  941.     write_result_file();
  942.     update_field_index();
  943.     update_dateindex();
  944.     update_registry($docid_count);
  945.     write_nmz_files();
  946.     make_slog_file();
  947.     remove_lockfile($var::NMZ{'lock'});
  948.     }
  949.     make_headfoot_pages($docid_count, $key_count);
  950.     put_log($total_files_size, $start_time, $docid_count, $key_count);
  951.     util::remove_tmpfiles();
  952.     unlink $var::NMZ{'_flist'};
  953. }
  954.  
  955. sub make_headfoot_pages($$) {
  956.     my ($docid_count, $key_count) = @_;
  957.  
  958.     for my $file (glob "$TEMPLATEDIR/NMZ.head*") {
  959.     $file =~ m!.*/NMZ.head(.*)$!;
  960.     my $suffix = $1;
  961.     make_headfoot("$var::NMZ{'head'}${suffix}", $docid_count, $key_count);
  962.     }
  963.     for my $file (glob "$TEMPLATEDIR/NMZ.foot*") {
  964.     $file =~ m!.*/NMZ.foot(.*)$!;
  965.     my $suffix = $1;
  966.     make_headfoot("$var::NMZ{'foot'}${suffix}", $docid_count, $key_count);
  967.     }
  968. }
  969.  
  970. # Parse command line options.
  971. sub parse_options
  972. {
  973.     if (@ARGV == 0) {
  974.     show_mini_usage();
  975.     exit 1;
  976.     }
  977.  
  978.     my @targets = ();
  979.     my $targets_loaded = 0;
  980.     my @argv = @ARGV;
  981.     my $cwd = cwd();
  982.  
  983.     my $opt_dummy = 0;
  984.     my $opt_version = 0;
  985.     my $opt_help = 0;
  986.     my $opt_all = 0;
  987.     my $opt_chasen = 0;
  988.     my $opt_chasen_noun = 0;
  989.     my $opt_kakasi = 0;
  990.     my $opt_checkpoint_sub = 0;
  991.     my $opt_show_config = 0;
  992.     my $opt_mailnews = 0;
  993.     my $opt_mhonarc = 0;
  994.  
  995.     my $opt_quiet = undef;
  996.     my $opt_config = undef;
  997.     my $output_dir = undef;
  998.     my $update_index = undef;
  999.     my $include_file = undef;
  1000.     my $target_list = undef;
  1001.     my $index_lang = undef;
  1002.  
  1003. #    Getopt::Long::Configure('bundling');
  1004.     Getopt::Long::config('bundling');
  1005.     GetOptions(
  1006.                   '0|help'              => \$opt_help,
  1007.            '1|exclude=s'         => \$conf::EXCLUDE_PATH,
  1008.            '2|deny=s'            => \$conf::DENY_FILE,
  1009.            '3|allow=s'           => \$conf::ALLOW_FILE,
  1010.            '4|update=s'          => \$update_index,
  1011.            '5|mhonarc'           => \$opt_mhonarc,
  1012.            '6|mtime=s'           => \$var::Opt{'mtime'},
  1013.            '7|html-split'        => \$var::Opt{'htmlsplit'},
  1014.            'C|show-config'       => \$opt_show_config,
  1015.            'E|no-edge-symbol'    => \$var::Opt{'noedgesymbol'},
  1016.            'F|target-list=s'     => \$target_list,
  1017.            'G|no-okurigana'      => \$var::Opt{'okurigana'},
  1018.            'H|no-hiragana'       => \$var::Opt{'hiragana'},
  1019.            'I|include=s'         => \$include_file,
  1020.            'K|no-symbol'         => \$var::Opt{'nosymbol'},
  1021.            'L|indexing-lang=s'     => \$index_lang,
  1022.            'M|meta'              => \$var::Opt{'meta'},
  1023.            'O|output-dir=s'      => \$output_dir,
  1024.            'S|checkpoint-sub'    => \$opt_checkpoint_sub,
  1025.            'T|template-dir=s'    => \$TEMPLATEDIR,
  1026.            'U|no-encode-uri'     => \$var::Opt{'noencodeuri'} ,
  1027.            'V|verbose'           => \$var::Opt{'verbose'},
  1028.            'Y|no-delete'         => \$var::Opt{'nodelete'},
  1029.            'Z|no-update'         => \$var::Opt{'noupdate'},
  1030.            'a|all'               => \$opt_all,
  1031.            'c|use-chasen'        => \$opt_chasen,
  1032.            'd|debug'             => \$var::Opt{'debug'},
  1033.            'e|robots'            => \$var::Opt{'robotexclude'},
  1034.            'f|config=s'          => \$opt_config,
  1035.            'h|mailnews'          => \$opt_mailnews,
  1036.            'k|use-kakasi'        => \$opt_kakasi,
  1037.            'm|use-chasen-noun'   => \$opt_chasen_noun,
  1038.            'q|quiet'             => \$opt_quiet,
  1039.            'r|replace=s'         => \$ReplaceCode,
  1040.            's|checkpoint'        => \$CheckPoint{'on'},
  1041.            't|media-type=s'      => \$MediaType,
  1042.            'u|uuencode'          => \$opt_dummy, # for backward compat.
  1043.            'v|version'           => \$opt_version,
  1044.            'x|no-heading-summary'=> \$var::Opt{'noheadabst'},
  1045.            );
  1046.  
  1047.     if ($opt_quiet) {
  1048.     # Make STDOUT quiet by redirecting STDOUT to null device.
  1049.     my $devnull = util::devnull();
  1050.     open(STDOUT, ">$devnull") || die "$devnull: $!";
  1051.     }
  1052.  
  1053.     if ($opt_config) {
  1054.     load_rcfile($ConfigFile = $opt_config);
  1055.     }
  1056.     load_filtermodules(); # to make effect $opt_config.
  1057.     postload_modules();
  1058.  
  1059.     if ($index_lang) {
  1060.     $util::LANG = $index_lang;
  1061.       util::dprint("Override indexing language: $util::LANG\n");
  1062.     }
  1063.  
  1064.     if ($opt_help) {
  1065.     show_usage();
  1066.     exit 1;
  1067.     }
  1068.  
  1069.     if ($opt_version) {
  1070.     show_version();
  1071.     exit 1;
  1072.     }
  1073.  
  1074.     if ($opt_show_config) {
  1075.     show_config();
  1076.     exit 1;
  1077.     }
  1078.  
  1079.     if (defined $update_index) {
  1080.     unless (-d $update_index) {
  1081.         print _("No such index: "), "$update_index\n";
  1082.         exit 1;
  1083.     }
  1084.  
  1085.     my $orig_status = $var::NMZ{'status'};
  1086.     $var::NMZ{'status'} = "$update_index/$var::NMZ{'status'}";
  1087.  
  1088.     my $argv = get_status("argv");
  1089.     @ARGV = split /\t/, $argv;
  1090.     util::dprint(_("Inherited argv: ")."@ARGV\n");
  1091.  
  1092.     my $cwd  = get_status("cwd");
  1093.     chdir $cwd;
  1094.     util::dprint(_("Inherited cwd: ")."$cwd\n");
  1095.  
  1096.     ($output_dir, @targets) = parse_options();
  1097.     $output_dir = $update_index;
  1098.     $var::NMZ{'status'} = $orig_status;  # See also change_filenames()
  1099.     return ($output_dir, @targets);
  1100.     }
  1101.  
  1102.     if ($opt_mailnews) {
  1103.     $MediaType = 'message/rfc822';
  1104.     }
  1105.     if ($opt_mhonarc) {
  1106.     $MediaType = 'text/html; x-type=mhonarc';
  1107.     }
  1108.     if ($opt_all) {
  1109.     $conf::ALLOW_FILE = ".*";
  1110.     }
  1111.     if ($opt_chasen) {
  1112.     $conf::WAKATI = $conf::CHASEN;
  1113.     $var::Opt{'noun'} = 0;
  1114.     }
  1115.     if ($opt_chasen_noun) {
  1116.     $conf::WAKATI = $conf::CHASEN_NOUN;
  1117.     $var::Opt{'noun'} = 1;
  1118.     }
  1119.     if ($opt_kakasi) {
  1120.     $conf::WAKATI = $conf::KAKASI;
  1121.     $var::Opt{'noun'} = 0;
  1122.     }
  1123.     if ($include_file) {
  1124.     do $include_file;
  1125.         util::dprint("Included: $include_file\n");
  1126.     }
  1127.     if ($target_list) {
  1128.     if ($CheckPoint{'continue'}) {
  1129.         @targets = ("dummy");
  1130.     } else {
  1131.         @targets = load_target_list($target_list);
  1132.         util::dprint(_("Loaded: ")."$target_list\n");
  1133.     }
  1134.     $targets_loaded = 1;
  1135.     }
  1136.     if ($opt_checkpoint_sub) {
  1137.     $CheckPoint{'on'}           = 1;
  1138.     $CheckPoint{'continue'}     = 1;
  1139.     @argv = grep {! /^-S$/} @argv;  # remove -S
  1140.     }
  1141.  
  1142.     if (defined $ReplaceCode) {
  1143.     my $orig = "/foo/bar/baz/quux.html";
  1144.     $_ = $orig;
  1145.     eval $ReplaceCode;
  1146.     if ($@) {  # eval error
  1147.         util::cdie(_("Invalid replace: ")."$ReplaceCode\n");
  1148.     }
  1149.     util::dprint(_("Replace: ")."$orig -> $_\n");
  1150.     }
  1151.  
  1152.     if (@ARGV == 0 && $targets_loaded == 0) {
  1153.     show_mini_usage();
  1154.     exit 1;
  1155.     }
  1156.  
  1157.     $output_dir = $cwd unless defined $output_dir;
  1158.     util::cdie("$output_dir: "._("invalid output directory\n"))
  1159.     unless (-d $output_dir && -w $output_dir);
  1160.  
  1161.     if ($SYSTEM eq "MSWin32" || $SYSTEM eq "os2") {
  1162.     # convert \ to / with consideration for Shift_JIS Kanji code
  1163.     $output_dir =~ 
  1164.         s!([\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]|[\x01-\x7f])!
  1165.         $1 eq "\\" ? "/" : $1!gex;
  1166.     }
  1167.  
  1168.     # take remaining @ARGV as targets
  1169.     if (@ARGV > 0 && $targets_loaded == 0) {
  1170.     @targets = @ARGV ;
  1171.     }
  1172.     
  1173.     # revert @ARGV
  1174.     # unshift @ARGV, splice(@argv, 0, @argv - @ARGV);
  1175.     @ARGV = @argv;
  1176.  
  1177.     return ($output_dir, @targets);
  1178. }
  1179.  
  1180. sub show_config () {
  1181.     print _("Loaded rcfile: ") . "@LoadedRcfiles\n" if @LoadedRcfiles;
  1182.     print _("System: ") . "$SYSTEM\n" if $SYSTEM;
  1183.     print _("Namazu: ") . "$var::VERSION\n" if $var::VERSION;
  1184.     print _("Perl: ") . "$]\n" if $];   # '$]' has a perl version
  1185.     print _("NKF: ") . "$conf::NKF\n" if $conf::NKF;
  1186.     print _("KAKASI: ") . "$conf::KAKASI\n" if $conf::KAKASI;
  1187.     print _("ChaSen: ") . "$conf::CHASEN\n" if $conf::CHASEN;
  1188.     print _("Wakati: ") . "$conf::WAKATI\n" if $conf::WAKATI;
  1189.     print _("Lang_Msg: ") . "$util::LANG_MSG\n";
  1190.     print _("Lang: ") . "$util::LANG\n";
  1191.     print _("Coding System: ") . "$CodingSystem\n";
  1192.     print _("CONFDIR: ") . "$CONFDIR\n";
  1193.     print _("LIBDIR: ") . "$LIBDIR\n";
  1194.     print _("FILTERDIR: ") . "$FILTERDIR\n";
  1195.     print _("TEMPLATEDIR: ") . "$TEMPLATEDIR\n";
  1196.  
  1197.     my @supported = sort grep { $var::Supported{$_} eq "yes" }
  1198.     keys %var::Supported;
  1199.     print _("Supported media types: \n");
  1200.     for my $mtype (@supported) {
  1201.     print "  $mtype\n";
  1202.     }
  1203. }
  1204.  
  1205. sub load_target_list ($) {
  1206.     my ($file) = @_;
  1207.     my $fh_targets = util::efopen($file);
  1208.     my @targets = <$fh_targets>;
  1209.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  1210.     grep { 
  1211.         s/\r//g; 
  1212.         # Replace \ with / with consideration for Shift_JIS.
  1213.         s!([\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]|[\x01-\x7f])!
  1214.         $1 eq "\\" ? "/" : $1!gex;
  1215.     } @targets;
  1216.     }
  1217.     chomp @targets; 
  1218.     return @targets;
  1219. }
  1220.  
  1221. # convert a relative path into an absolute path
  1222. sub absolute_path($$) {
  1223.     my ($cwd, $path) = @_;
  1224.  
  1225.     $path =~ s!^\.$!\./!;
  1226.     $path =~ s!^\.[/\\]!$cwd/!;
  1227.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  1228.         $path =~ s!([\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]|[\x01-\x7f])!
  1229.         $1 eq "\\" ? "/" : $1!gex;
  1230.         $path =~ s,^([A-Z](?!:/)),$cwd/$1,i;
  1231.     } else {
  1232.         $path =~ s!^([^/])!$cwd/$1!; 
  1233.     }
  1234.     return $path;
  1235. }
  1236.  
  1237. sub find_target (@) {
  1238.     my @targets = @_;
  1239.  
  1240.     my $cwd = cwd();
  1241.     @targets = map { absolute_path($cwd, $_) } @targets;
  1242.  
  1243.     # Convert \ to / with consideration for Shift_JIS encoding.
  1244.     if (($SYSTEM eq "MSWin32") || ($SYSTEM eq "os2")) {
  1245.     grep {
  1246.         $_ =~ s!([\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]|[\x01-\x7f])!
  1247.         $1 eq "\\" ? "/" : $1!gex;
  1248.     } @targets;
  1249.     }
  1250.  
  1251.     # For reporting effects of --allow, --deny, --exclude, --mtime
  1252.     # options in --verbose mode.
  1253.     my %counts = (); 
  1254.     $counts{'possible'} = 0;
  1255.     $counts{'excluded'} = 0;
  1256.     $counts{'too_old'} = 0;
  1257.     $counts{'too_new'} = 0;
  1258.     $counts{'not_allowed'} = 0;
  1259.     $counts{'denied'} = 0;
  1260.  
  1261.     # Traverse directories.
  1262.     # This routine is not efficent but I prefer reliable logic.
  1263.     my @flist = ();
  1264.     my $start = time();
  1265.     util::vprint(_("find_target starting: "). localtime($start). "\n");
  1266.     while (@targets) {
  1267.     my $target = shift @targets;
  1268.  
  1269.     if ($target eq '') {
  1270.         print STDERR "Warning: target contains empty line, skip it\n";
  1271.         next;
  1272.     }
  1273.     
  1274.     if (-f $target) { # target is a file.
  1275.         add_target($target, \@flist, \%counts);
  1276.     } elsif (-d $target) { # target is a directory.
  1277.         my @subtargets = ();
  1278.         # Find subdirectories in target directory
  1279.         # because File::Find::find() does not follow symlink.
  1280.         if (-l $target) {
  1281.         my $dh = new DirHandle($target);
  1282.         while (defined(my $ent = $dh->read)) {
  1283.             next if ($ent =~ /^\.{1,2}$/);
  1284.             my $fname = "$target/$ent";
  1285.             next if ($fname eq '.' || $fname eq '..');
  1286.             if (-d $fname) {
  1287.             push(@subtargets, $fname);
  1288.             } else {
  1289.             add_target($fname, \@flist, \%counts);
  1290.             }
  1291.         }
  1292.         } else {
  1293.         @subtargets = ($target);
  1294.         }
  1295.  
  1296.         #
  1297.         # Wanted routine for File::Find's find().
  1298.         #
  1299.         my $wanted_closure = sub {
  1300.         my $fname = "$File::Find::dir/$_";
  1301.         add_target($fname, \@flist, \%counts);
  1302.         };
  1303.  
  1304.         find($wanted_closure, @subtargets) if (@subtargets > 0);
  1305.     } else {
  1306.         print STDERR _("unsupported target: ") . $target;
  1307.     }
  1308.     }
  1309.  
  1310.     # uniq @flist
  1311.     my %mark = ();
  1312.     @flist = grep {$mark{$_}++; $mark{$_} == 1} @flist;
  1313.  
  1314.     # Sort file names with consideration for numbers.
  1315.     @flist = map  { $_->[0] }
  1316.          sort { $a->[1] cmp $b->[1] } 
  1317.          map  { my $tmp = $_; $tmp =~ s/(\d+)/sprintf("%08d", $1)/ge; 
  1318.             [ $_, $tmp ] } @flist;
  1319.  
  1320.     my $elapsed = time() - $start ;
  1321.     $elapsed += 1 ;   # to round up and avoid 0 
  1322.  
  1323.     # For --verbose option.
  1324.     report_find_target($elapsed, $#flist + 1, %counts);
  1325.  
  1326.     return @flist;
  1327. }
  1328.  
  1329. sub add_target ($\@\%) {
  1330.     my ($target, $flists_ref, $counts_ref) = @_;
  1331.  
  1332.     if ($target =~ /[\n\r\t]/) {
  1333.     $target =~ s/[\n\r\t]//g;
  1334.     print STDERR "Warning: $target contains LF/CR/TAB chars, skip it\n";
  1335.     return;   # skip a file name containing LF/CR/TAB chars.
  1336.     }
  1337.  
  1338.     return unless -f $target;  # Only file is targeted.
  1339.  
  1340.     $counts_ref->{'possible'}++;
  1341.  
  1342.     unless (-r $target) {
  1343.         util::vprint(sprintf(_("Unreadable:    %s"), $target));
  1344.     $counts_ref->{'excluded'}++;
  1345.     return;
  1346.     }
  1347.  
  1348.  
  1349.     if (defined $conf::EXCLUDE_PATH && 
  1350.     $target =~ /$conf::EXCLUDE_PATH/ ) 
  1351.     {
  1352.         util::vprint(sprintf(_("Excluded:    %s"), $target));
  1353.     $counts_ref->{'excluded'}++;
  1354.     return; 
  1355.     }
  1356.  
  1357.     #
  1358.     # Do processing just like find's  --mtime option.
  1359.     #
  1360.     if (defined $var::Opt{'mtime'}) {
  1361.     my $mtime = -M $_;
  1362.     if ($var::Opt{'mtime'} < 0) {
  1363.  
  1364.         # This must be `>=' not `>' for consistency with find(1).
  1365.         if (int($mtime) >= - $var::Opt{'mtime'}) {
  1366.             util::vprint(sprintf(_("Too old:    %s"), $target));
  1367.             $counts_ref->{'too_old'}++;
  1368.         return;
  1369.         }
  1370.     } elsif ($var::Opt{'mtime'} > 0) {
  1371.         if ($var::Opt{'mtime'} =~ /^\+/) {
  1372.         if ((int($mtime) < $var::Opt{'mtime'})) {
  1373.             util::vprint(sprintf(_("Too new:    %s"), $target));
  1374.             $counts_ref->{'too_new'}++;
  1375.             return;
  1376.         }
  1377.         } else {
  1378.         if (int($mtime) != $var::Opt{'mtime'}) {
  1379.             if (int($mtime) > $var::Opt{'mtime'}) {
  1380.                 util::vprint(sprintf(_("Too old:    %s"),$target));
  1381.             $counts_ref->{'too_old'}++;
  1382.             } else {
  1383.                 util::vprint(sprintf(_("Too new:    %s"),$target));
  1384.             $counts_ref->{'too_new'}++;
  1385.             }
  1386.             return;
  1387.         }
  1388.         }
  1389.     } else {
  1390.         # $var::Opt{'mtime'} == 0 ;
  1391.         return;
  1392.     }
  1393.     }
  1394.  
  1395.     # Extract the file name of the target.
  1396.     $target =~ m!^.*/([^/]+)$!;
  1397.     my $fname = $1;
  1398.  
  1399.     if ($fname =~ m!^($conf::DENY_FILE)$!i ) {
  1400.         util::vprint(sprintf(_("Denied:    %s"), $target));
  1401.     $counts_ref->{'denied'}++; 
  1402.     return;
  1403.     }
  1404.     if ($fname !~ m!^($conf::ALLOW_FILE)$!i) {
  1405.         util::vprint(sprintf(_("Not allowed:    %s"), $target));
  1406.     $counts_ref->{'not_allowed'}++; 
  1407.     return;
  1408.     } else{
  1409.         util::vprint(sprintf(_("Targeted:    %s"), $target));
  1410.     push @$flists_ref, $target;
  1411.     }
  1412.  
  1413. }
  1414.  
  1415. sub report_find_target ($$%) {
  1416.     my ($elapsed, $num_targeted, %counts) = @_;
  1417.  
  1418.     util::vprint(_("find_target finished: ") . localtime(time()). "\n");
  1419.     util::vprint(sprintf(_("Target Files: %d (Scan Performance: Elapsed Sec.: %d, Files/sec: %.1f)"), 
  1420.              $num_targeted, $elapsed, 
  1421.              $num_targeted /$elapsed));
  1422.     util::vprint(sprintf(_("  Possible: %d, Not allowed: %d, Denied: %d, Excluded: %d"), 
  1423.              $counts{'possible'}, 
  1424.              $counts{'not_allowed'},  
  1425.              $counts{'denied'}, 
  1426.              $counts{'excluded'}));
  1427.     util::vprint(sprintf(_("  MTIME too old: %d, MTIME too new: %d"),
  1428.              $counts{'too_old'}, 
  1429.              $counts{'too_new'}));
  1430. }
  1431.  
  1432. sub show_usage () {
  1433.     util::dprint(_("lang_msg: ")."$util::LANG_MSG\n");
  1434.     util::dprint(_("lang: ")."$util::LANG\n");
  1435.  
  1436.     # To know why we should do this, see usage.pl.
  1437.     my $usage = $usage::USAGE;
  1438.     $usage =~ s/\n\n/\n/g; 
  1439.     $usage = _($usage);
  1440.     printf "$usage", $var::VERSION, $var::MAILING_ADDRESS;
  1441. }
  1442.  
  1443. sub show_mini_usage () {
  1444.     print _("Usage: mknmz [options] <target>...\n");
  1445.     print _("Try `mknmz --help' for more information.\n");
  1446. }
  1447.  
  1448. sub show_version () {
  1449.     print $usage::VERSION_INFO;
  1450. }
  1451.  
  1452. #
  1453. # check the file. No $msg is good.
  1454. #
  1455. sub check_file ($$$$$) {
  1456.     my ($cfile, $cfile_size, $text_size, $mtype, $uri) = @_;
  1457.  
  1458.     my $msg = undef;
  1459.     if (! -e $cfile) {
  1460.     $msg = _("does NOT EXIST! skipped.");
  1461.     } elsif (! -r $cfile) {
  1462.     $msg = _("is NOT READABLE! skipped.");
  1463.     } elsif ($text_size == 0 || $cfile_size == 0) {
  1464.     $msg = _("is 0 size! skipped.");
  1465.     } elsif ($mtype =~ /^application\/octet-stream/) {
  1466.     $msg = _("may be a BINARY file! skipped.");
  1467.     } elsif ($text_size > $conf::TEXT_SIZE_MAX) {
  1468.     $msg = _("is too LARGE a text! skipped.");
  1469.     } elsif ($mtype =~ /; x-system=unsupported$/) {
  1470.     $mtype =~ s/; x-system=unsupported$//;
  1471.     $msg = _("Unsupported media type ")."($mtype)"._(" skipped.");
  1472.     } elsif ($mtype =~ /; x-error=.*$/) {
  1473.     $mtype =~ s/^.*; x-error=(.*)$/$1/;
  1474.     $msg = $mtype;
  1475.     } elsif ($mtype =~ /^x-system/) {
  1476.     $msg = _("system error occurred! ")."($mtype)".(" skipped.");
  1477.     } 
  1478.  
  1479.     return $msg;
  1480. }
  1481.  
  1482.  
  1483. #
  1484. # Write NMZ.version file.
  1485. #
  1486. sub write_version() {
  1487.     unless (-f $var::NMZ{'version'}) {
  1488.     my $fh = util::efopen(">$var::NMZ{'version'}");
  1489.     print $fh "Namazu-Index-Version: $NAMAZU_INDEX_VERSION\n";
  1490.     }
  1491. }
  1492.  
  1493. #
  1494. # rename each temporary file to a real file name.
  1495. #
  1496. sub write_nmz_files () {
  1497.     util::Rename($var::NMZ{'_i'},   $var::NMZ{'i'});
  1498.     util::Rename($var::NMZ{'_ii'}, $var::NMZ{'ii'});
  1499.     util::Rename($var::NMZ{'_w'},  $var::NMZ{'w'});
  1500.     util::Rename($var::NMZ{'_wi'}, $var::NMZ{'wi'});
  1501.     util::Rename($var::NMZ{'_p'},  $var::NMZ{'p'});
  1502.     util::Rename($var::NMZ{'_pi'}, $var::NMZ{'pi'});
  1503. }
  1504.  
  1505. # output NMZ.body
  1506. sub write_body_msg () {
  1507.     for my $file (glob "$TEMPLATEDIR/NMZ.body*") {
  1508.     $file =~ m!.*/NMZ.body(.*)$!;
  1509.     my $suffix = $1;
  1510.     write_message("$var::NMZ{'body'}${suffix}");
  1511.     }
  1512. }
  1513.  
  1514. # output NMZ.tips
  1515. sub write_tips_msg () {
  1516.     for my $file (glob "$TEMPLATEDIR/NMZ.tips*") {
  1517.     $file =~ m!.*/NMZ.tips(.*)$!;
  1518.     my $suffix = $1;
  1519.     write_message("$var::NMZ{'tips'}${suffix}");
  1520.     }
  1521. }
  1522.  
  1523.  
  1524. # output NMZ.result.*
  1525. sub write_result_file () {
  1526.     my $fname = "NMZ.result.normal";
  1527.  
  1528.     my @files = glob "$TEMPLATEDIR/NMZ.result.*";
  1529.  
  1530.     for my $file (@files) {
  1531.     $file =~ m!(NMZ\.result\.[^/]*)$!;
  1532.     my $target = "$var::OUTPUT_DIR/$1";
  1533.     if (-f $target) {  # already exist;
  1534.         next;
  1535.     } else {
  1536.         my $buf = util::readfile($file);
  1537.         my $fh_file = util::efopen(">$target");
  1538.         print $fh_file $buf;
  1539.     }
  1540.     }
  1541. }
  1542.  
  1543. # write NMZ.body and etc.
  1544. sub write_message ($) {
  1545.     my ($msgfile) = @_;
  1546.  
  1547.     if (! -f $msgfile) {
  1548.     my ($template, $fname);
  1549.     
  1550.     $msgfile =~ m!.*/(.*)$!;
  1551.     $fname = $1;
  1552.     $template = "$TEMPLATEDIR/$fname";
  1553.  
  1554.     if (-f $template) {
  1555.         my $buf = util::readfile($template);
  1556.         my $fh_output = util::efopen(">$msgfile");
  1557.         print $fh_output $buf;
  1558.     }
  1559.     }
  1560. }
  1561.  
  1562.  
  1563. #
  1564. # Make the NMZ.slog file for logging.
  1565. #
  1566. sub make_slog_file () {
  1567.     {
  1568.     my $fh_slogfile = util::efopen(">>$var::NMZ{'slog'}");
  1569.     }
  1570.     chmod 0666, $var::NMZ{'slog'};
  1571. }
  1572.  
  1573.  
  1574. #
  1575. # Concatenate $CURRENTDIR to the head of each file.
  1576. #
  1577. sub change_filenames ($) {
  1578.     my $dir = $var::OUTPUT_DIR;
  1579.  
  1580.     for my $key (sort keys %var::NMZ) {
  1581.     next if $key =~ /^_/;    # exclude temporary file
  1582.     $var::NMZ{$key} = "$dir/$var::NMZ{$key}";
  1583.     }
  1584.  
  1585.     # temporary files
  1586.     for my $key (sort keys %var::NMZ) {
  1587.     if ($key =~ /^_/) {
  1588.         $var::NMZ{$key} = util::tmpnam($var::NMZ{$key});
  1589.     }
  1590.     }
  1591.  
  1592.     if ($var::Opt{'debug'}) {
  1593.     for my $key (sort keys %var::NMZ) {
  1594.         util::dprint("NMZ: $var::NMZ{$key}\n");
  1595.     }
  1596.     }
  1597. }
  1598.  
  1599.  
  1600. #
  1601. # Preparation processing for appending index files.
  1602. #
  1603. sub append_index (@) {
  1604.     my @flist = @_;
  1605.  
  1606.     my $docid_base = 0;
  1607.     ($docid_base, @flist) = set_target_files(@flist);
  1608.  
  1609.     unless (@flist) {     # if @flist is empty
  1610.     if ($DeletedFilesCount > 0) {
  1611.         make_headfoot_pages(0, 0);
  1612.         set_lockfile($var::NMZ{'lock2'});
  1613.         update_dateindex();
  1614.         update_registry(0);
  1615.         put_log(0, 0, 0, get_total_keys());
  1616.         util::remove_tmpfiles();
  1617.     }
  1618.     print _("No files to index.\n");
  1619.     exit 0;
  1620.     }
  1621.  
  1622.     $APPENDMODE = 1;
  1623.     # conserve files by copying
  1624.     copy($var::NMZ{'i'},  $var::NMZ{'_i'});
  1625.     copy($var::NMZ{'w'},  $var::NMZ{'_w'});
  1626.     copy($var::NMZ{'t'},  $var::NMZ{'_t'}) 
  1627.     unless -f $var::NMZ{'_t'}; # preupdated ?
  1628.     copy($var::NMZ{'p'},  $var::NMZ{'_p'});
  1629.     copy($var::NMZ{'pi'}, $var::NMZ{'_pi'});
  1630.  
  1631.     return ($docid_base, @flist);
  1632. }
  1633.  
  1634. #
  1635. # Set target files to @flist and return with the number of regiested files.
  1636. #
  1637. sub set_target_files() {
  1638.     my %rdocs;    # 'rdocs' means 'registered documents'
  1639.     my @found_files = @_;
  1640.  
  1641.     # Load the list of registered documents
  1642.     $rdocs{'name'} = load_registry();
  1643.  
  1644.     # Pick up overlapped documents and do marking
  1645.     my %mark1;
  1646.     my @overlapped_files;
  1647.     grep {$_ !~ /^\# / && $mark1{$_}++ } @{$rdocs{'name'}};
  1648.     $rdocs{'overlapped'} = {}; # Prepare an anonymous hash.
  1649.     for my $overlapped (grep { $mark1{$_} } @found_files) {
  1650.     $rdocs{'overlapped'}{$overlapped} = 1;
  1651.     push @overlapped_files, $overlapped;
  1652.     };
  1653.  
  1654.     # Pick up not overlapped documents which are files to index.
  1655.     my @flist = grep { ! $mark1{$_} } @found_files;
  1656.      
  1657.     if ($var::Opt{'noupdate'}) {
  1658.     return (scalar @{$rdocs{'name'}}, @flist);
  1659.     };
  1660.  
  1661.     # Load the date index.
  1662.     $rdocs{'mtime'} = load_dateindex();
  1663.  
  1664.     if (@{$rdocs{'mtime'}} == 0) {
  1665.     return (scalar @{$rdocs{'name'}}, @flist); 
  1666.     };
  1667.  
  1668.     util::assert(@{$rdocs{'name'}} == @{$rdocs{'mtime'}},
  1669.          "NMZ.r ($#{$rdocs{'name'}}) and NMZ.t ($#{$rdocs{'mtime'}}) are not consistent!");
  1670.  
  1671.     # Pick up deleted documents and do marking
  1672.     # (registered in the NMZ.r but not existent in the filesystem)
  1673.     my @deleted_documents;
  1674.     unless ($var::Opt{'nodelete'}) {
  1675.     my %mark2;
  1676.     grep { $mark2{$_}++ } @found_files;
  1677.     for my $deleted (grep { $_ !~ /^\# / && ! $mark2{$_} && 
  1678.                 ! $rdocs{'overlapped'}{$_} } 
  1679.              @{$rdocs{'name'}}) 
  1680.     {
  1681.         $rdocs{'deleted'}{$deleted} = 1;
  1682.         push @deleted_documents, $deleted;
  1683.     }
  1684.     }
  1685.  
  1686.     # Pick up updated documents and set the missing number for deleted files.
  1687.     my @updated_documents = pickup_updated_documents(\%rdocs);
  1688.  
  1689.     # Append updated files to the list of files to index.
  1690.     if (@updated_documents) {
  1691.     push @flist, @updated_documents;
  1692.     }
  1693.  
  1694.     # Remove duplicates.
  1695.     my %seen = ();
  1696.     @flist = grep { ! $seen{$_}++ } @flist;
  1697.  
  1698.     util::dprint(_("\n\n== found files ==\n"), join("\n", @found_files), "\n");
  1699.     util::dprint(_("\n\n== registered documents ==\n"), join("\n", @{$rdocs{'name'}}), "\n");
  1700.     util::dprint(_("\n\n== overlapped documents ==\n"), join("\n", @overlapped_files), "\n");
  1701.     util::dprint(_("\n\n== deleted documents ==\n"), join("\n", @deleted_documents), "\n");
  1702.     util::dprint(_("\n\n== updated documents ==\n"), join("\n", @updated_documents), "\n");
  1703.     util::dprint(_("\n\n== files to index ==\n"), join("\n", @flist), "\n");
  1704.  
  1705.     # Update NMZ.t with the missing number infomation and
  1706.     # append updated files and deleted files to NMZ.r with leading '# '
  1707.     if (@updated_documents || @deleted_documents) {
  1708.     $DeletedFilesCount = 0;
  1709.     $UpdatedFilesCount = 0;
  1710.     $UpdatedFilesCount += @updated_documents;
  1711. #    $DeletedFilesCount += @updated_documents;
  1712.     $DeletedFilesCount += @deleted_documents;
  1713.     preupdate_dateindex(@{$rdocs{'mtime'}});
  1714.     preupdate_registry(@updated_documents, @deleted_documents);
  1715.     }
  1716.  
  1717.     # Return the number of registered documents and list of files to index.
  1718.     return (scalar @{$rdocs{'name'}}, @flist);
  1719. }
  1720.  
  1721. sub preupdate_registry(@) {
  1722.     my (@list) = @_;
  1723.  
  1724.     my $fh_registry = util::efopen(">$var::NMZ{'_r'}");
  1725.     @list = grep { s/(.*)/\# $1\n/ } @list;
  1726.     print $fh_registry @list;
  1727.     print $fh_registry &_("## deleted: ") . util::rfc822time(time()) . "\n\n";
  1728. }
  1729.  
  1730. sub preupdate_dateindex(@) {
  1731.     my @mtimes = @_;
  1732.  
  1733.     # Since rewriting the entire file, it is not efficient, 
  1734.     # but simple and reliable. this would be revised in the future.
  1735.     my $fh_dateindex = util::efopen(">$var::NMZ{'_t'}");
  1736. #    print "\nupdate_dateindex\n", join("\n", @mtimes), "\n\n";
  1737.     print $fh_dateindex pack("N*", @mtimes);
  1738. }
  1739.  
  1740. sub update_registry ($) {
  1741.     my ($docid_count) = @_;
  1742.  
  1743.     {
  1744.     my $fh_registry = util::efopen(">>$var::NMZ{'r'}");
  1745.     my $fh_registry_ = util::efopen($var::NMZ{'_r'});
  1746.     while (defined(my $line = <$fh_registry_>)) {
  1747.         print $fh_registry $line;
  1748.     }
  1749.     if ($docid_count > 0) {
  1750.         print $fh_registry &_("## indexed: ") . util::rfc822time(time()) . "\n\n";
  1751.     }
  1752.     }
  1753.     unlink $var::NMZ{'_r'};
  1754. }
  1755.  
  1756. sub update_dateindex () {
  1757.     util::Rename($var::NMZ{'_t'}, $var::NMZ{'t'});
  1758. }
  1759.  
  1760. sub update_field_index () {
  1761.     my @list = glob "$var::NMZ{'field'}.*.tmp";
  1762.     for my $tmp (@list) {
  1763.     if ($tmp =~ m!((^.*/NMZ\.field\.[^\.]+(?:\.i)?)\.tmp)!) {
  1764.         my $fname_tmp = $1;
  1765.         my $fname_out = $2;
  1766.         {
  1767.         my $fh_field = util::efopen(">>$fname_out");
  1768.         my $fh_tmp = util::efopen($fname_tmp);
  1769.  
  1770.         while (defined(my $line = <$fh_tmp>)) {
  1771.             print $fh_field $line;
  1772.         }
  1773.         }
  1774.         unlink $fname_tmp;
  1775.     } else {
  1776.         util::cdie(_("update_field_index: ")."@list");
  1777.     }
  1778.     }
  1779. }
  1780.  
  1781. sub pickup_updated_documents (\%) {
  1782.     my ($rdocs_ref) = @_;
  1783.     my @updated_documents = ();
  1784.  
  1785.     # To avoid duplicated outputs caused by --html-split support.
  1786.     my %printed = ();
  1787.     my $i = 0;
  1788.     for my $cfile (@{$rdocs_ref->{'name'}}) {
  1789.     if (defined($rdocs_ref->{'deleted'}{$cfile})) {
  1790.         unless ($printed{$cfile}) {
  1791.         print "$cfile " . _("was deleted!\n");
  1792.         $printed{$cfile} = 1;
  1793.         }
  1794.         $rdocs_ref->{'mtime'}[$i] = -1; # Assign the missing number.
  1795.     } elsif (defined($rdocs_ref->{'overlapped'}{$cfile})) {
  1796.         my $cfile_mtime = (stat($cfile))[9];
  1797.         my $rfile_mtime = $rdocs_ref->{'mtime'}[$i];
  1798.  
  1799.         if ($rfile_mtime != $cfile_mtime) {
  1800.         # The file is updated!
  1801.         unless ($printed{$cfile}) {
  1802.             print "$cfile " . _("was updated!\n");
  1803.             $printed{$cfile} = 1;
  1804.         }
  1805.         push(@updated_documents, $cfile);
  1806.         $rdocs_ref->{'mtime'}[$i] = -1; # Assign the missing number.
  1807.         }
  1808.     }
  1809.     $i++;
  1810.     }
  1811.  
  1812.     return @updated_documents
  1813. }
  1814.  
  1815. sub load_dateindex() {
  1816.     my $fh_dateindex = util::efopen($var::NMZ{'t'});
  1817.  
  1818.     my $size = -s $var::NMZ{'t'};
  1819.     my $buf  = "";
  1820.     read($fh_dateindex, $buf, $size);
  1821.     my @list = unpack("N*", $buf);  # load date index
  1822. #    print "\nload_dateindex\n", join("\n", @list), "\n\n";
  1823.  
  1824.     return [ @list ];
  1825. }
  1826.  
  1827. sub load_registry () {
  1828.     my $fh_registry = util::efopen($var::NMZ{'r'});
  1829.  
  1830.     my @list = ();
  1831.     my %deleted    = ();
  1832.     my @registered = ();
  1833.  
  1834.     while (defined(my $line = <$fh_registry>)) {
  1835.     chomp($line);
  1836.     next if $line =~ /^\s*$/;  # an empty line
  1837.     next if $line =~ /^##/;    # a comment
  1838.     if ($line =~ s/^\#\s+//) { # deleted document
  1839.         $deleted{$line}++;
  1840.     } else {
  1841.         # Remove HTML's anchor generated by --html-split option.
  1842.         $line =~ s/\t.*$//g;  
  1843.         push @registered, $line;
  1844.     }
  1845.     }
  1846.  
  1847.     # Exclude deleted documents.
  1848.     for my $doc (@registered) {
  1849.     if ($deleted{$doc}) {
  1850.         push @list, "# $doc";
  1851.         $deleted{$doc}--;
  1852.     } else {
  1853.         push @list, $doc;
  1854.     }
  1855.     }
  1856.  
  1857.     return [ @list ];
  1858. }
  1859.  
  1860. sub get_total_keys() {
  1861.     my $keys = get_status("keys");
  1862.     $keys = 0 unless defined $keys;
  1863.     return $keys;
  1864. }
  1865.  
  1866. sub get_total_files() {
  1867.     my $files = get_status("files");
  1868.     $files = 0 unless defined $files;
  1869.     return $files;
  1870. }
  1871.  
  1872. sub get_status($) {
  1873.     my ($key) = @_;
  1874.  
  1875.     my $fh = util::fopen($var::NMZ{'status'});
  1876.     return undef unless defined $fh;
  1877.  
  1878.     while (defined(my $line = <$fh>)) {
  1879.     if ($line =~ /^$key\s+(.*)$/) {
  1880.         util::dprint("status: $key = $1\n");
  1881.         $fh->close;
  1882.         return $1;
  1883.     }
  1884.     }
  1885.     return undef;
  1886. }
  1887.  
  1888. sub put_total_files($) {
  1889.     my ($number) = @_;
  1890.     $number =~ tr/,//d;
  1891.     put_status("files", $number);
  1892. }
  1893.  
  1894. sub put_total_keys($) {
  1895.     my ($number) = @_;
  1896.     $number =~ tr/,//d;
  1897.     put_status("keys", $number);
  1898. }
  1899.  
  1900. sub put_status($$) {
  1901.     my ($key, $value) = @_;
  1902.  
  1903.     # remove NMZ.status file if the file has a previous value.
  1904.     unlink $var::NMZ{'status'} if defined get_status($key);
  1905.  
  1906.     my $fh = util::efopen(">> $var::NMZ{'status'}");
  1907.     print $fh "$key $value\n";
  1908. }
  1909.  
  1910. # do logging
  1911. sub put_log ($$$$) {
  1912.     my ($total_files_size, $start_time, $docid_count, $total_keys_count) = @_;
  1913.  
  1914.     my $date = localtime;
  1915.     my $added_files_count   = $docid_count - $UpdatedFilesCount;
  1916.     my $deleted_documents_count = $DeletedFilesCount;
  1917.     my $updated_documents_count = $UpdatedFilesCount;
  1918.     my $total_files_count   = get_total_files() + $docid_count 
  1919.                   - $DeletedFilesCount - $UpdatedFilesCount;
  1920.     my $added_keys_count    = 0;
  1921.     $added_keys_count       = $total_keys_count - get_total_keys();
  1922.  
  1923.     my $processtime         = time - $start_time;
  1924.     $processtime            = 0 if $start_time == 0;
  1925.     $total_files_size       = $total_files_size;
  1926.     $total_keys_count       = $total_keys_count;
  1927.  
  1928.     my @logmsgs = ();
  1929.     if ($APPENDMODE) {
  1930.     push @logmsgs, N_("[Append]");
  1931.     } else {
  1932.     push @logmsgs, N_("[Base]");
  1933.     }
  1934.     push @logmsgs, N_("Date:"), "$date" if $date;
  1935.     push @logmsgs, N_("Added Documents:"), util::commas("$added_files_count")
  1936.     if $added_files_count;
  1937.     push @logmsgs, N_("Deleted Documents:"), 
  1938.     util::commas("$deleted_documents_count") if $deleted_documents_count;
  1939.     push @logmsgs, N_("Updated Documents:"), 
  1940.     util::commas("$updated_documents_count") if $updated_documents_count;
  1941.     push @logmsgs, N_("Size (bytes):"), util::commas("$total_files_size")
  1942.     if $total_files_size;
  1943.     push @logmsgs, N_("Total Documents:"), util::commas("$total_files_count") 
  1944.     if $total_files_count;
  1945.     push @logmsgs, N_("Added Keywords:"), util::commas("$added_keys_count") 
  1946.     if $added_keys_count;
  1947.     push @logmsgs, N_("Total Keywords:"), util::commas("$total_keys_count") 
  1948.     if $total_keys_count;
  1949.     push @logmsgs, N_("Wakati:"), "$conf::WAKATI" if $conf::WAKATI;
  1950.     push @logmsgs, N_("Time (sec):"), util::commas("$processtime")
  1951.     if $processtime;
  1952.     push @logmsgs, N_("File/Sec:"),  sprintf "%.2f", 
  1953.         (($added_files_count + $updated_documents_count) / $processtime) 
  1954.     if $processtime;
  1955.     push @logmsgs, N_("System:"), "$SYSTEM" if $SYSTEM;
  1956.     push @logmsgs, N_("Perl:"),   "$]" if $];   # '$]' has a perl version
  1957.     push @logmsgs, N_("Namazu:"), "$var::VERSION" if $var::VERSION;
  1958.  
  1959.     my $log_for_file = "";
  1960.  
  1961.     my $msg = shift @logmsgs;    # [Base] or [Append]
  1962.     # To stdout, use gettext.
  1963.     print _($msg), "\n";
  1964.     # To log file, do not use gettext.
  1965.     $log_for_file = $msg . "\n";
  1966.     while (@logmsgs) {
  1967.     my $field = shift @logmsgs;
  1968.     my $value = shift @logmsgs;
  1969.     printf "%-20s %s\n", _($field), "$value";
  1970.     $log_for_file .= sprintf "%-20s %s\n", $field, "$value";
  1971.     }
  1972.     print "\n";
  1973.     $log_for_file .= "\n";
  1974.  
  1975.     put_log_to_logfile($log_for_file);
  1976.     put_total_files($total_files_count);
  1977.     put_total_keys($total_keys_count);
  1978.  
  1979.     my $argv = join "\t", @ARGV;
  1980.     my $cwd  = cwd();
  1981.     put_status("argv", $argv);
  1982.     put_status("cwd",  $cwd);
  1983. }
  1984.  
  1985. sub put_log_to_logfile ($) {
  1986.     my ($logmsg) = @_;
  1987.     my $fh_logfile = util::efopen(">>$var::NMZ{'log'}");
  1988.     print $fh_logfile $logmsg;
  1989. }
  1990.  
  1991. sub get_year() {
  1992.     my $year = (localtime)[5] + 1900;
  1993.  
  1994.     return $year;
  1995. }
  1996.  
  1997. # Compose NMZ.head and NMZ.foot. Prepare samples if necessary.
  1998. # Insert $docid_count, $key_count, and $month/$day/$year respectively.
  1999. sub make_headfoot ($$$) {
  2000.     my ($file, $docid_count, $key_count) = @_;
  2001.  
  2002.     my $day   = sprintf("%02d", (localtime)[3]);
  2003.     my $month = sprintf("%02d", (localtime)[4] + 1);
  2004.     my $year  = get_year();
  2005.     my $buf   = "";
  2006.  
  2007.     if (-f $file) {
  2008.     $buf = util::readfile($file);
  2009.     } else {
  2010.     $file =~ m!.*/(.*)$!;
  2011.     my $fname = $1;
  2012.     my $template = "$TEMPLATEDIR/$fname";
  2013.  
  2014.     if (-f $template) {
  2015.         $buf = util::readfile($template);
  2016.     } else {
  2017.         return;
  2018.     }
  2019.     }
  2020.  
  2021.     my $fh_file = util::efopen(">$file");
  2022.  
  2023.     if ($buf =~ /(<!-- FILE -->)\s*(.*)\s*(<!-- FILE -->)/) {
  2024.     my $total_files_count = util::commas(get_total_files() + $docid_count 
  2025.                    - $DeletedFilesCount - $UpdatedFilesCount);
  2026.     $buf =~ s/(<!-- FILE -->)(.*)(<!-- FILE -->)/$1 $total_files_count $3/;
  2027.  
  2028.     }
  2029.     if ($buf =~ /(<!-- KEY -->)\s*(.*)\s*(<!-- KEY -->)/) {
  2030.     my $tmp = $2;
  2031.     $tmp =~ tr/,//d;
  2032.     $tmp = $key_count;
  2033.     $tmp = util::commas($tmp);
  2034.     $buf =~ s/(<!-- KEY -->)(.*)(<!-- KEY -->)/$1 $tmp $3/;
  2035.     }
  2036.     $buf =~ s#(<!-- DATE -->)(.*)(<!-- DATE -->)#$1 $year-$month-$day $3#gs;
  2037.     $buf =~ s/(<!-- VERSION -->)(.*)(<!-- VERSION -->)/$1 v$var::VERSION $3/gs;
  2038.     $buf =~ s{(<!-- ADDRESS -->)(.*)(<!-- ADDRESS -->)}
  2039.          {$1\n<a href="mailto:$conf::ADDRESS">$conf::ADDRESS</a>\n$3}gs;
  2040.     $buf =~ s{(<!-- LINK-REV-MADE -->)(.*)(<!-- LINK-REV-MADE -->)}
  2041.          {$1\n<link rev=made href="mailto:$conf::ADDRESS">\n$3}gs;
  2042.  
  2043.     print $fh_file $buf;
  2044. }
  2045.  
  2046. # Make phrase hashes for NMZ.p
  2047. # Handle two words each for calculating a hash value ranged 0-65535.
  2048. sub make_phrase_hash ($$$) {
  2049.     my ($docid_count, $docid_base, $contref) = @_;
  2050.  
  2051.     my %tmp = ();
  2052.     $$contref =~ s!\x7f */? *\d+ *\x7f!!g;  # remove tags of weight
  2053.     $$contref =~ tr/\xa1-\xfea-z0-9 \n//cd; # remove all symbols
  2054.     my @words = split(/\s+/, $$contref);
  2055.     @words = grep {$_ ne ""} @words;   # remove empty words
  2056.     my $word_b = shift @words;
  2057.     my $docid = $docid_count + $docid_base;
  2058.     for my $word (@words) {
  2059.     my $hash = hash($word_b . $word);
  2060.     unless (defined $tmp{$hash}) {
  2061.         $tmp{$hash} = 1;
  2062.         $PhraseHashLast{$hash} = 0 unless defined $PhraseHashLast{$hash};
  2063.         $PhraseHash{$hash} .= pack("w", $docid - $PhraseHashLast{$hash});
  2064. #        util::dprint("<$word_b, $word> $hash\n");
  2065.         $PhraseHashLast{$hash} = $docid;
  2066.     }
  2067.     $word_b = $word;
  2068.     }
  2069. }
  2070.  
  2071. # Construct NMZ.p and NMZ.pi file. this processing is rather complex.
  2072. sub write_phrase_hash () {
  2073.     write_phrase_hash_sub();
  2074.     util::Rename($var::NMZ{'__p'}, $var::NMZ{'_p'});
  2075.     util::Rename($var::NMZ{'__pi'}, $var::NMZ{'_pi'});
  2076. }
  2077.  
  2078. sub write_phrase_hash_sub () {
  2079.     my $opened = 0;
  2080.  
  2081.     return 0 if %PhraseHash eq "0";
  2082.     util::dprint(_("doing write_phrase_hash() processing.\n"));
  2083.  
  2084.     my $fh_tmp_pi = util::efopen(">$var::NMZ{'__pi'}");
  2085.     my $fh_tmp_p  = util::efopen(">$var::NMZ{'__p'}");
  2086.  
  2087.     my $fh_phrase = util::fopen($var::NMZ{'_p'});
  2088.     my $fh_phraseindex;
  2089.     if ($fh_phrase) {
  2090.     $fh_phraseindex = util::efopen($var::NMZ{'_pi'});
  2091.     $opened = 1;
  2092.     }
  2093.     
  2094.     my $ptr = 0;
  2095.     for (my $i = 0; $i < 65536; $i++) {
  2096.  
  2097.     my $baserecord = "";
  2098.     my $baseleng = 0;
  2099.  
  2100.     if ($opened) {
  2101.         my $tmp = 0;
  2102.         read($fh_phraseindex, $tmp, $var::INTSIZE);
  2103.         $tmp = unpack("N", $tmp);
  2104.         if ($tmp != 0xffffffff) { # 0xffffffff
  2105.         $baseleng = readw($fh_phrase);
  2106.         read($fh_phrase, $baserecord, $baseleng);
  2107.         }
  2108.     }
  2109.     if (defined($PhraseHash{$i})) {
  2110.         if ($baserecord eq "") {
  2111.         print $fh_tmp_pi pack("N", $ptr);
  2112.         my $record = $PhraseHash{$i};
  2113.         my $n2 = length($record);
  2114.         my $data = pack("w", $n2) . $record;
  2115.         print $fh_tmp_p $data;
  2116.         $ptr += length($data);
  2117.         } else {
  2118.         print $fh_tmp_pi pack("N", $ptr);
  2119.         my $record = $PhraseHash{$i};
  2120.         my $last_docid = get_last_docid($baserecord, 1);
  2121.         my $adjrecord = adjust_first_docid($record, $last_docid);
  2122.         check_records(\$record, \$baserecord, 1) unless defined $record; # namazu-bugs-ja#31
  2123.         $record = $adjrecord;
  2124.         my $n2 = length($record) + $baseleng;
  2125.         my $data = pack("w", $n2) .  $baserecord . $record;
  2126.         print $fh_tmp_p $data;
  2127.         $ptr += length($data);
  2128.         }
  2129.     } else {
  2130.         if ($baserecord eq "") {
  2131.         # if $baserecord has no data, set to 0xffffffff
  2132.         print $fh_tmp_pi pack("N", 0xffffffff);
  2133.         } else {
  2134.         print $fh_tmp_pi pack("N", $ptr);
  2135.         my $data = pack("w", $baseleng) . $baserecord;
  2136.         print $fh_tmp_p $data;
  2137.         $ptr += length($data);
  2138.         }
  2139.     }
  2140.     }
  2141.     %PhraseHash = ();
  2142.     %PhraseHashLast = ();
  2143. }
  2144.  
  2145. # Dr. Knuth's  ``hash'' from (UNIX MAGAZINE May 1998)
  2146. sub hash ($) {
  2147.     my ($word) = @_;
  2148.  
  2149.     my $hash = 0;
  2150.     for (my $i = 0; $word ne ""; $i++) {
  2151.     $hash ^= $Seed[$i & 0x03][ord($word)];
  2152.         $word = substr $word, 1;
  2153.     # $word =~ s/^.//;  is slower
  2154.     }
  2155.     return $hash & 65535;
  2156. }
  2157.  
  2158. # Count frequencies of words.
  2159. sub count_words ($$$$) {
  2160.     my ($docid_count, $docid_base, $contref, $kanji) = @_;
  2161.     my (@tmp);
  2162.  
  2163.     # Normalize into small letter.
  2164.     $$contref =~ tr/A-Z/a-z/;
  2165.  
  2166.     # Do wakatigaki if necessary.
  2167.     if (util::islang("ja")) {
  2168.     wakati::wakatize_japanese($contref) if $kanji;
  2169.     }
  2170.  
  2171.     # Remove all symbols when -K option is specified.
  2172.     $$contref =~ tr/\xa1-\xfea-z0-9/   /c if $var::Opt{'nosymbol'};
  2173.  
  2174.     my $part1 = "";
  2175.     my $part2 = "";
  2176.     if ($$contref =~ /\x7f/) {
  2177.     $part1 = substr $$contref, 0, index($$contref, "\x7f");
  2178.     $part2 = substr $$contref, index($$contref, "\x7f");
  2179. #    $part1 = $PREMATCH;  # $& and friends are not efficient
  2180. #    $part2 = $MATCH . $POSTMATCH;
  2181.     } else {
  2182.     $part1 = $$contref;
  2183.     $part2 = "";
  2184.     }
  2185.  
  2186.     # do scoring
  2187.     my %word_count = ();
  2188.     $part2 =~ s!\x7f *(\d+) *\x7f([^\x7f]*)\x7f */ *\d+ *\x7f!
  2189.     wordcount_sub($2, $1, \%word_count)!ge;
  2190.     wordcount_sub($part1, 1, \%word_count);
  2191.  
  2192.     # Add them to whole index
  2193.     my $docid = $docid_count + $docid_base;
  2194.     for my $word (keys(%word_count)) {
  2195.     next if ($word eq "" || length($word) > $conf::WORD_LENG_MAX);
  2196.     $KeyIndexLast{$word} = 0 unless defined $KeyIndexLast{$word};
  2197.     $KeyIndex{$word} .= pack("w2", 
  2198.                  $docid - $KeyIndexLast{$word}, 
  2199.                  $word_count{$word});
  2200.     $KeyIndexLast{$word} = $docid;
  2201.     }
  2202. }
  2203.  
  2204. #
  2205. # Count words and do score weighting
  2206. #
  2207. sub wordcount_sub ($$\%) {
  2208.     my ($text, $weight, $word_count) = @_;
  2209.  
  2210.     # Count frequencies of words in a current document.
  2211.     # Handle symbols as follows.
  2212.     #
  2213.     # tcp/ip      ->  tcp/ip,     tcp,      ip
  2214.     # (tcp/ip)    ->  (tcp/ip),   tcp/ip,   tcp, ip
  2215.     # ((tcpi/ip)) ->  ((tcp/ip)), (tcp/ip), tcp
  2216.     #
  2217.     # Don't do processing for nested symbols.
  2218.     # NOTE: When -K is specified, all symbols are already removed.
  2219.  
  2220.     my @words = split /\s+/, $text;
  2221.     for my $word (@words) {
  2222.     next if $word eq "";
  2223.     if ($var::Opt{'noedgesymbol'}) {
  2224.         # remove symbols at both ends
  2225.         $word =~ s/^[^\xa1-\xfea-z_0-9]*(.*?)[^\xa1-\xfea-z_0-9]*$/$1/g;
  2226.     }
  2227.     $word_count->{$word} = 0 unless defined($word_count->{$word});
  2228.     $word_count->{$word} += $weight;
  2229.     unless ($var::Opt{'nosymbol'}) {
  2230.         if ($word =~ /^[^\xa1-\xfea-z_0-9](.+)[^\xa1-\xfea-z_0-9]$/) {
  2231.         $word_count->{$1} = 0 unless defined($word_count->{$1});
  2232.         $word_count->{$1} += $weight;
  2233.         next unless $1 =~ /[^\xa1-\xfea-z_0-9]/;
  2234.         } elsif ($word =~ /^[^\xa1-\xfea-z_0-9](.+)/) {
  2235.         $word_count->{$1} = 0 unless defined($word_count->{$1});
  2236.         $word_count->{$1} += $weight;
  2237.         next unless $1 =~ /[^\xa1-\xfea-z_0-9]/;
  2238.         } elsif ($word =~ /(.+)[^\xa1-\xfea-z_0-9]$/) {
  2239.         $word_count->{$1} = 0 unless defined($word_count->{$1});
  2240.         $word_count->{$1} += $weight;
  2241.         next unless $1 =~ /[^\xa1-\xfea-z_0-9]/;
  2242.         }
  2243.         my @words_ = split(/[^\xa1-\xfea-z_0-9]+/, $word)
  2244.         if $word =~ /[^\xa1-\xfea-z_0-9]/;
  2245.         for my $tmp (@words_) {
  2246.         next if $tmp eq "";
  2247.         $word_count->{$tmp} = 0 unless defined($word_count->{$tmp});
  2248.         $word_count->{$tmp} += $weight;
  2249.         }
  2250.         @words_ = ();
  2251.     }
  2252.     }
  2253.     return "";
  2254. }
  2255.  
  2256. # Construct NMZ.i and NMZ.ii file. this processing is rather complex.
  2257. sub write_index () {
  2258.     my $key_count = write_index_sub();
  2259.     util::Rename($var::NMZ{'__i'}, $var::NMZ{'_i'});
  2260.     util::Rename($var::NMZ{'__w'}, $var::NMZ{'_w'});
  2261.  
  2262.     return $key_count;
  2263. }
  2264.  
  2265. # readw: read one pack 'w' word.
  2266. # This code was contributed by <furukawa@tcp-ip.or.jp>.
  2267. sub readw ($) {
  2268.     my $fh = shift;
  2269.     my $ret = '';
  2270.     my $c;
  2271.     
  2272.     while (read($fh, $c, 1)){
  2273.     $ret .= $c;
  2274.     last unless 0x80 & ord $c;
  2275.     }
  2276.     return unpack('w', $ret);
  2277. }
  2278.  
  2279. sub get_last_docid ($$) {
  2280.     my ($record, $step) = @_;
  2281.     my (@data) = unpack 'w*', $record;
  2282.  
  2283.     my $sum = 0;
  2284.     for (my $i = 0; $i < @data; $i += $step) {
  2285.     $sum += $data[$i];
  2286.     }
  2287.     my $leng = @data / $step;
  2288.     return $sum;
  2289. }
  2290.  
  2291. sub adjust_first_docid ($$) {
  2292.     my ($record, $last_docid) = @_;
  2293.     my (@data) = unpack 'w*', $record;
  2294.  
  2295.     $data[0] = $data[0] - $last_docid;
  2296.     return undef if ($data[0] < 0); # namazu-bug-ja#31
  2297.     $record = pack 'w*', @data;
  2298.     return $record;
  2299. }
  2300.  
  2301. sub write_index_sub () {
  2302.     my @words = sort keys(%KeyIndex);
  2303.     return 0 if $#words == -1;
  2304.  
  2305.     my $cnt = 0;
  2306.     my $ptr_i = 0;
  2307.     my $ptr_w = 0;
  2308.     my $key_count = 0;
  2309.     my $baserecord = "";
  2310.  
  2311.     util::dprint(_("doing write_index() processing.\n"));
  2312.     my $fh_tmp_i  = util::efopen(">$var::NMZ{'__i'}");
  2313.     my $fh_tmp_w  = util::efopen(">$var::NMZ{'__w'}");
  2314.     my $fh_i      = util::fopen($var::NMZ{'_i'});
  2315.     my $fh_ii     = util::efopen(">$var::NMZ{'_ii'}");
  2316.     my $fh_w      = util::fopen($var::NMZ{'_w'});
  2317.     my $fh_wi = util::efopen(">$var::NMZ{'_wi'}");
  2318.  
  2319.     if ($fh_w) {
  2320.       FOO:
  2321.     while (defined(my $line = <$fh_w>)) {
  2322.         chop $line;
  2323.         my $current_word = $line;
  2324.  
  2325.         my $baseleng = readw($fh_i);
  2326.         read($fh_i, $baserecord, $baseleng);
  2327.  
  2328.          for (; $cnt < @words; $cnt++) {
  2329.         last unless $words[$cnt] le $current_word;
  2330.         my $record = $KeyIndex{$words[$cnt]};
  2331.         my $leng = length($record);
  2332.  
  2333.         if ($current_word eq $words[$cnt]) {
  2334.             my $last_docid = get_last_docid($baserecord, 2);
  2335.             my $adjrecord = adjust_first_docid($record, $last_docid);
  2336.             check_records(\$record, \$baserecord, 2) unless defined $record; # namazu-bugs-ja#31
  2337.             $record = $adjrecord;
  2338.             $leng = length($record);  # re-measure
  2339.             my $tmp = pack("w", $leng + $baseleng);
  2340.  
  2341.             my $data_i = "$tmp$baserecord$record";
  2342.             my $data_w = "$current_word\n";
  2343.             print $fh_tmp_i $data_i;
  2344.             print $fh_tmp_w $data_w;
  2345.             print $fh_ii pack("N", $ptr_i);
  2346.             print $fh_wi pack("N", $ptr_w);
  2347.             $ptr_i += length($data_i);
  2348.             $ptr_w += length($data_w);
  2349.             $key_count++;
  2350.  
  2351.             $cnt++;
  2352.             next FOO;
  2353.         } else {
  2354.             my $tmp = pack("w", $leng);
  2355.             my $data_i = "$tmp$record";
  2356.             my $data_w = "$words[$cnt]\n";
  2357.             print $fh_tmp_i $data_i;
  2358.             print $fh_tmp_w $data_w;
  2359.             print $fh_ii pack("N", $ptr_i);
  2360.             print $fh_wi pack("N", $ptr_w);
  2361.             $ptr_i += length($data_i);
  2362.             $ptr_w += length($data_w);
  2363.             $key_count++;
  2364.         }
  2365.         }
  2366.         my $tmp  = pack("w", $baseleng);
  2367.         my $data_i = "$tmp$baserecord";
  2368.         my $data_w = "$current_word\n";
  2369.         print $fh_tmp_i $data_i;
  2370.         print $fh_tmp_w $data_w;
  2371.         print $fh_ii pack("N", $ptr_i);
  2372.         print $fh_wi pack("N", $ptr_w);
  2373.         $ptr_i += length($data_i);
  2374.         $ptr_w += length($data_w);
  2375.         $key_count++;
  2376.     }
  2377.     }
  2378.     while ($cnt < @words) {
  2379.     my $leng = length($KeyIndex{$words[$cnt]});
  2380.     my $tmp = pack("w", $leng);
  2381.     my $record = $KeyIndex{$words[$cnt]};
  2382.  
  2383.     my $data_i = "$tmp$record";
  2384.     my $data_w = "$words[$cnt]\n";
  2385.     print $fh_tmp_i $data_i;
  2386.     print $fh_tmp_w $data_w;
  2387.     print $fh_ii pack("N", $ptr_i);
  2388.     print $fh_wi pack("N", $ptr_w);
  2389.     $ptr_i += length($data_i);
  2390.     $ptr_w += length($data_w);
  2391.     $key_count++;
  2392.     $cnt++;
  2393.     }
  2394.     %KeyIndex = ();
  2395.     %KeyIndexLast = ();
  2396.  
  2397.     return $key_count;
  2398. }
  2399.  
  2400. #
  2401. # Decide the media type. 
  2402. # FIXME: Very ad hoc. It's just a compromise. -- satoru
  2403. #
  2404. sub decide_type ($$) {
  2405.     my ($name, $cont) = @_;
  2406.     return $name if (!defined $cont || $name eq $cont);
  2407.  
  2408.     util::dprint("decide_type: name: $name, cont: $cont\n");
  2409.     if ($cont =~ m!^text/plain! && $name =~ m!^text/plain!) {
  2410.     return $name;
  2411.     } elsif ($cont =~ m!^application/octet-stream!) {
  2412.     return $name;
  2413.     } elsif ($cont =~ m!^application/(excel|powerpoint|msword)! &&
  2414.          $name !~ m!^application/octet-stream!)  {
  2415.     # FIXME: Currently File::MMagic 1.02's checktype_data() 
  2416.     # is unreliable for them.
  2417.     return $name;
  2418.     }
  2419.  
  2420.     return $cont;
  2421. }
  2422.  
  2423. #
  2424. # Debugging code for the "negative numbers" problem.
  2425. #
  2426. sub check_records ($$$) {
  2427.     my ($recref, $baserecref, $step) = @_;
  2428.     dump_record($baserecref, $step);
  2429.     dump_record($recref, $step);
  2430.     print STDERR "The \x22negative number\x22 problem occurred.\n";
  2431.     exit(1);
  2432. }
  2433.  
  2434. sub dump_record($$) {
  2435.     my ($recref, $step) = @_;
  2436.     my (@data) = unpack 'w*', $$recref;
  2437.     print STDERR "dump record data to NMZ.bug.info (step: $step)...";
  2438.     my $fh_info = util::fopen(">> NMZ.bug.info");
  2439.     print $fh_info "dumped record data (step: $step)...";
  2440.     foreach (@data) {
  2441.     print $fh_info sprintf(" %08x", $_);
  2442.     }
  2443.     print $fh_info "\n";
  2444.     return;
  2445. }
  2446.  
  2447. #
  2448. # For avoiding "used only once: possible typo at ..." warnings.
  2449. #
  2450. muda($conf::ON_MEMORY_MAX,
  2451.      $conf::WORD_LENG_MAX, $conf::TEXT_SIZE_MAX,
  2452.      $conf::DENY_FILE, $var::INTSIZE,
  2453.      $conf::CHASEN_NOUN, $conf::CHASEN,
  2454.      $conf::KAKASI, $var::Opt{'okurigana'},
  2455.      $var::Opt{'hiragana'}, $conf::DIRECTORY_INDEX,
  2456.      $usage::USAGE, $var::Opt{'noheadabst'}, $usage::VERSION_INFO,
  2457.      $var::Opt{'noencodeurl'}, $conf::HTML_SUFFIX,
  2458.      $var::RECURSIVE_ACTIONS, $conf::META_TAGS, $var::USE_NKF_MODULE,
  2459.      $conf::ADDRESS, $var::MAILING_ADDRESS,
  2460.      $conf::FILE_SIZE_MAX,
  2461.      );
  2462.  
  2463. sub muda {}
  2464.  
  2465.